//! A crate for talking to J-Link debug probes connected via USB.
//!
//! This crate allows access to the vendor-specific USB interface used to control JTAG / SWD
//! operations and other functionality. It does *not* provide access to the virtual COM port
//! functionality (which is a regular CDC device, so no special support is needed).
//!
//! Inspired by [libjaylink] (though this library is not a port).
//!
//! [libjaylink]: https://repo.or.cz/libjaylink.git
//!
//! # Pinout
//!
//! J-Link uses a pinout based on the standard 20-pin ARM JTAG connector, extended for SWD
//! compatibility and with pins for UART.
//!
//! JTAG pinout:
//!
//! ```notrust
//! ┌───────────┐
//! VTref │ * 1 2 * │ NC
//! nTRST │ * 3 4 * │ GND
//! TDI │ * 5 6 * │ GND
//! TMS │ * 7 8 * │ GND
//! TCK ┌┘ * 9 10 * │ GND
//! RTCK └┐ * 11 12 * │ GND
//! TDO │ * 13 14 * │ GND
//! RESET │ * 15 16 * │ GND
//! DBGRQ │ * 17 18 * │ GND
//! 5V-Supply │ * 19 20 * │ GND
//! └───────────┘
//! ```
//!
//! SWD (+ UART) pinout:
//!
//! ```notrust
//! ┌───────────┐
//! VTref │ * 1 2 * │ NC
//! - │ * 3 4 * │ GND
//! J-Link TX │ * 5 6 * │ GND
//! SWDIO │ * 7 8 * │ GND
//! SWCLK ┌┘ * 9 10 * │ GND
//! - └┐ * 11 12 * │ GND
//! SWO │ * 13 14 * │ GND
//! RESET │ * 15 16 * │ GND
//! J-Link RX │ * 17 18 * │ GND
//! 5V-Supply │ * 19 20 * │ GND
//! └───────────┘
//! ```
//!
//! PIC32 ICSP pinout (untested):
//!
//! ```notrust
//! ┌───────────┐
//! VTref │ * 1 2 * │ NC
//! - │ * 3 4 * │ GND
//! - │ * 5 6 * │ GND
//! PGED │ * 7 8 * │ GND
//! PGEC ┌┘ * 9 10 * │ GND
//! - └┐ * 11 12 * │ GND
//! - │ * 13 14 * │ GND
//! RESET │ * 15 16 * │ GND
//! - │ * 17 18 * │ GND
//! 5V-Supply │ * 19 20 * │ GND
//! └───────────┘
//! ```
//!
//! # Reference
//!
//! Segger has released a PDF documenting the USB protocol: "Reference manual for J-Link USB
//! Protocol" (Document RM08001-R2).
//!
//! The archive.org version is the most up-to-date one.
#![doc(html_root_url = "https://docs.rs/jaylink/0.3.0")]
// Deny a few warnings in doctests, since rustdoc `allow`s many warnings by default
#![doc(test(attr(deny(unused_imports, unused_must_use))))]
#![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)]
// We use explicit lifetimes to make APIs easier to understand (this also affects rustdoc)
#![allow(clippy::needless_lifetimes)]
#[macro_use]
mod macros;
mod bits;
mod capabilities;
mod error;
mod interface;
pub use self::bits::BitIter;
pub use self::capabilities::{Capabilities, Capability};
pub use self::error::{Error, ErrorKind};
pub use self::interface::{Interface, InterfaceIter, Interfaces};
use self::bits::IteratorExt as _;
use self::error::ResultExt as _;
use bitflags::bitflags;
use byteorder::{LittleEndian, ReadBytesExt};
use io::Cursor;
use log::{debug, trace, warn};
use std::cell::{Cell, RefCell, RefMut};
use std::convert::{TryFrom, TryInto};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use std::{
cmp, fmt,
io::{self, Read},
ops::Deref,
thread,
};
/// A result type with the error hardwired to [`Error`].
pub type Result<T> = std::result::Result<T, Error>;
const VID_SEGGER: u16 = 0x1366;
const TIMEOUT_DEFAULT: Duration = Duration::from_millis(500);
#[repr(u8)]
#[allow(dead_code)]
enum Command {
Version = 0x01,
GetSpeeds = 0xC0,
GetMaxMemBlock = 0xD4,
GetCaps = 0xE8,
GetCapsEx = 0xED,
GetHwVersion = 0xF0,
GetState = 0x07,
GetHwInfo = 0xC1,
GetCounters = 0xC2,
MeasureRtckReact = 0xF6,
ResetTrst = 0x02,
SetSpeed = 0x05,
SelectIf = 0xC7,
SetKsPower = 0x08,
HwClock = 0xC8,
HwTms0 = 0xC9,
HwTms1 = 0xCA,
HwData0 = 0xCB,
HwData1 = 0xCC,
HwJtag = 0xCD,
HwJtag2 = 0xCE,
HwJtag3 = 0xCF,
HwJtagWrite = 0xD5,
HwJtagGetResult = 0xD6,
HwTrst0 = 0xDE,
HwTrst1 = 0xDF,
Swo = 0xEB,
WriteDcc = 0xF1,
ResetTarget = 0x03,
HwReleaseResetStopEx = 0xD0,
HwReleaseResetStopTimed = 0xD1,
HwReset0 = 0xDC,
HwReset1 = 0xDD,
GetCpuCaps = 0xE9,
ExecCpuCmd = 0xEA,
WriteMem = 0xF4,
ReadMem = 0xF5,
WriteMemArm79 = 0xF7,
ReadMemArm79 = 0xF8,
ReadConfig = 0xF2,
WriteConfig = 0xF3,
}
#[repr(u8)]
enum SwoCommand {
Start = 0x64,
Stop = 0x65,
Read = 0x66,
GetSpeeds = 0x6E,
}
#[repr(u8)]
enum SwoParam {
Mode = 0x01,
Baudrate = 0x02,
ReadSize = 0x03,
BufferSize = 0x04,
// FIXME: Do these have hardware/firmware version requirements to be recognized?
}
/// The supported SWO data encoding modes.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u32)]
#[non_exhaustive]
pub enum SwoMode {
Uart = 0x00000000,
// FIXME: Manchester encoding?
}
bitflags! {
/// SWO status returned by probe on SWO buffer read.
struct SwoStatus: u32 {
/// The on-probe buffer has overflowed. Device data was lost.
const OVERRUN = 1 << 0;
}
}
impl SwoStatus {
fn new(bits: u32) -> Self {
let flags = SwoStatus::from_bits_truncate(bits);
if flags.bits() != bits {
warn!("Unknown SWO status flag bits: 0x{:08X}", bits);
}
flags
}
}
/// A handle to a J-Link USB device.
///
/// This is the main interface type of this library. There are multiple ways of obtaining an
/// instance of it:
///
/// * [`JayLink::open_by_serial`]: Either opens the only J-Link device connected to the computer, or
/// opens a specific one by its serial number. Recommended for applications that interact with one
/// J-Link device only (ie. most of them).
/// * [`JayLink::open_usb`]: Opens a specific J-Link device according to the given
/// [`UsbDeviceInfo`]. Also see [`scan_usb`].
pub struct JayLink {
handle: rusb::DeviceHandle<rusb::GlobalContext>,
read_ep: u8,
write_ep: u8,
cmd_buf: RefCell<Vec<u8>>,
/// The capabilities reported by the device. They're fetched once, when the device is opened.
caps: Capabilities,
/// The supported interfaces. Like `caps`, this is fetched once when opening the device.
interfaces: Interfaces,
/// The currently selected target interface. This is stored here to avoid unnecessary roundtrips
/// when performing target I/O operations.
interface: Interface,
manufacturer: String,
product: String,
serial: String,
}
impl JayLink {
/// Opens an attached J-Link device by its serial number.
///
/// If `serial` is `None`, this will open the only attached J-Link device, and return an error
/// of type [`ErrorKind::MultipleDevicesFound`] when more than one is attached. This is usually
/// the desired behavior of robust applications.
///
/// **Note**: Probes remember their selected interfaces between reconnections, so it is
/// recommended to always call [`JayLink::select_interface`] after opening a probe.
pub fn open_by_serial(serial: Option<&str>) -> Result<Self> {
let mut devices = scan_usb()?.filter_map(|usb_device| {
let dev = match usb_device.open() {
Ok(dev) => dev,
Err(_) => return None,
};
if let Some(serial) = serial {
if dev.serial_string() == serial {
Some(dev)
} else {
None
}
} else {
Some(dev)
}
});
let first = devices.next().ok_or_else(|| {
let message = if let Some(serial ) = serial {
format!("no J-Link device with serial {} was found (make sure your current user has permissions to access it)", serial)
} else {
"no J-Link devices found (make sure your current user has permissions to access them)".to_string()
};
Error::new(ErrorKind::DeviceNotFound, message)
})?;
if devices.next().is_some() {
let msg = if let Some(serial) = serial {
format!("found multiple devices matching serial {}", serial)
} else {
"multiple devices found (specify serial number to select one)".to_string()
};
return Err(Error::new(ErrorKind::MultipleDevicesFound, msg));
}
Ok(first)
}
/// Opens a specific J-Link USB device.
///
/// **Note**: Probes remember their selected interfaces between reconnections, so it is
/// recommended to always call [`JayLink::select_interface`] after opening a probe.
pub fn open_usb(usb_device: UsbDeviceInfo) -> Result<Self> {
// NB: We take `UsbDeviceInfo` by value since it isn't cloneable (yet), so taking it by-ref
// would lock us into a less flexible API. It should be easy to make it cloneable with a few
// changes to rusb though.
let descr = usb_device
.inner
.device_descriptor()
.expect("libusb_get_device_descriptor returned unexpected error");
let mut handle = usb_device.inner.open().map_err(|e| {
let inner: Box<dyn std::error::Error + Send + Sync> = if cfg!(windows)
&& (e == rusb::Error::NotSupported || e == rusb::Error::NotFound)
{
format!(
"{} (this error may be caused by not having the \
WinUSB driver installed; use Zadig (https://zadig.akeo.ie/) to install it \
for the J-Link device; this will replace the SEGGER J-Link driver)",
e
)
.into()
} else {
Box::new(e)
};
Error::with_while(ErrorKind::Usb, inner, "opening USB device")
})?;
debug!("open_usb: device descriptor: {:#x?}", descr);
if descr.num_configurations() != 1 {
warn!(
"device has {} configurations, expected 1",
descr.num_configurations()
);
}
let conf = handle
.active_configuration()
.jaylink_err_while("reading device configuration")?;
// Device configurations are 1-indexed, apparently
if conf != 1 {
warn!(
"device in configuration {}, expected 1; changing configuration",
conf
);
handle.set_active_configuration(1).jaylink_err()?;
}
let conf = usb_device
.inner
.active_config_descriptor()
.jaylink_err_while("reading device configuration descriptor")?;
debug!("scanning {} interfaces", conf.num_interfaces());
trace!("active configuration descriptor: {:#x?}", conf);
let mut jlink_intf = None;
for (i, intf) in conf.interfaces().enumerate() {
trace!("interface #{} descriptors:", i + 1);
for descr in intf.descriptors() {
trace!("{:#x?}", descr);
// We detect the proprietary J-Link interface using the vendor-specific class codes
// and the endpoint properties
if descr.class_code() == 0xff
&& descr.sub_class_code() == 0xff
&& descr.protocol_code() == 0xff
{
if let Some((intf, _, _)) = jlink_intf {
return Err(format!(
"found multiple matching USB interfaces ({} and {})",
intf,
descr.interface_number()
))
.jaylink_err();
}
let endpoints: Vec<_> = descr.endpoint_descriptors().collect();
trace!("endpoint descriptors: {:#x?}", endpoints);
if endpoints.len() != 2 {
warn!("vendor-specific interface with {} endpoints, expected 2 (skipping interface)", endpoints.len());
continue;
}
if !endpoints
.iter()
.all(|ep| ep.transfer_type() == rusb::TransferType::Bulk)
{
warn!(
"encountered non-bulk endpoints, skipping interface: {:#x?}",
endpoints
);
continue;
}
let (read_ep, write_ep) = if endpoints[0].direction() == rusb::Direction::In {
(endpoints[0].address(), endpoints[1].address())
} else {
(endpoints[1].address(), endpoints[0].address())
};
jlink_intf = Some((descr.interface_number(), read_ep, write_ep));
debug!("J-Link interface is #{}", descr.interface_number());
}
}
}
let (intf, read_ep, write_ep) = if let Some(intf) = jlink_intf {
intf
} else {
return Err("device is not a J-Link device".to_string()).jaylink_err();
};
handle
.claim_interface(intf)
.jaylink_err_while("taking control over USB device")?;
// Check that we're still in the expected configuration (another application could
// interfere).
// See: http://libusb.sourceforge.net/api-1.0/caveats.html
let conf = handle.active_configuration().jaylink_err()?;
if conf != 1 {
return Err("another application is accessing the device".to_string()).jaylink_err();
}
let mut this = Self {
manufacturer: handle
.read_manufacturer_string_ascii(&descr)
.jaylink_err()?,
product: handle.read_product_string_ascii(&descr).jaylink_err()?,
serial: handle
.read_serial_number_string_ascii(&descr)
.jaylink_err()?,
read_ep,
write_ep,
cmd_buf: RefCell::new(Vec::new()),
caps: Capabilities::from_raw_legacy(0), // dummy value
interface: Interface::Spi, // dummy value, must not be JTAG
interfaces: Interfaces::from_bits_warn(0), // dummy value
handle,
};
this.fill_capabilities()?;
this.fill_interfaces()?;
Ok(this)
}
/// Reads the advertised capabilities from the device.
fn fill_capabilities(&mut self) -> Result<()> {
self.write_cmd(&[Command::GetCaps as u8])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
let mut caps = Capabilities::from_raw_legacy(u32::from_le_bytes(buf));
debug!("legacy caps: {:?}", caps);
// If the `GET_CAPS_EX` capability is set, use the extended capability command to fetch
// all the capabilities.
if caps.contains(Capability::GetCapsEx) {
self.write_cmd(&[Command::GetCapsEx as u8])?;
let mut buf = [0; 32];
self.read(&mut buf)?;
let real_caps = Capabilities::from_raw_ex(buf);
if !real_caps.contains_all(caps) {
return Err(format!(
"ext. caps are not a superset of legacy caps (legacy: {:?}, ex: {:?})",
caps, real_caps
))
.jaylink_err();
}
debug!("extended caps: {:?}", real_caps);
caps = real_caps;
} else {
debug!("extended caps not supported");
}
self.caps = caps;
Ok(())
}
fn fill_interfaces(&mut self) -> Result<()> {
if !self.capabilities().contains(Capability::SelectIf) {
// Pre-SELECT_IF probes only support JTAG.
self.interfaces = Interfaces::single(Interface::Jtag);
self.interface = Interface::Jtag;
return Ok(());
}
self.write_cmd(&[Command::SelectIf as u8, 0xFF])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
let intfs = Interfaces::from_bits_warn(u32::from_le_bytes(buf));
self.interfaces = intfs;
Ok(())
}
/// Returns the manufacturer string stored in the device descriptor.
pub fn manufacturer_string(&self) -> &str {
&self.manufacturer
}
/// Returns the product string stored in the device descriptor.
pub fn product_string(&self) -> &str {
&self.product
}
/// Returns the serial number string stored in the device descriptor.
///
/// This serial number string can be passed to [`JayLink::open_by_serial`] to open a specific
/// J-Link device.
pub fn serial_string(&self) -> &str {
&self.serial
}
fn buf(&self, len: usize) -> RefMut<'_, Vec<u8>> {
let mut vec = self.cmd_buf.borrow_mut();
vec.resize(len, 0);
vec
}
fn write_cmd(&self, cmd: &[u8]) -> Result<()> {
trace!("write {} bytes: {:x?}", cmd.len(), cmd);
let bytes = self
.handle
.write_bulk(self.write_ep, cmd, TIMEOUT_DEFAULT)
.jaylink_err_while("writing data to device")?;
if bytes != cmd.len() {
return Err(format!(
"incomplete write (expected {} bytes, wrote {})",
cmd.len(),
bytes
))
.jaylink_err();
}
Ok(())
}
fn read(&self, buf: &mut [u8]) -> Result<()> {
let mut total = 0;
while total < buf.len() {
let buf = &mut buf[total..];
let bytes = self
.handle
.read_bulk(self.read_ep, buf, TIMEOUT_DEFAULT)
.jaylink_err_while("reading from device")?;
total += bytes;
}
trace!("read {} bytes: {:x?}", buf.len(), buf);
Ok(())
}
fn require_capability(&self, cap: Capability) -> Result<()> {
if self.capabilities().contains(cap) {
Ok(())
} else {
Err(Error::new(
ErrorKind::MissingCapability,
format!("device is missing capabilities ({:?}) for operation", cap),
))
}
}
fn require_interface_supported(&self, intf: Interface) -> Result<()> {
if self.interfaces.contains(intf) {
Ok(())
} else {
Err(Error::new(
ErrorKind::InterfaceNotSupported,
format!("probe does not support target interface {:?}", intf),
))
}
}
fn require_interface_selected(&self, intf: Interface) -> Result<()> {
if self.interface == intf {
Ok(())
} else {
Err(Error::new(
ErrorKind::Other,
format!("interface {} must be selected for this operation (currently using interface {})", intf, self.interface),
))
}
}
/// Reads the firmware version string from the device.
pub fn read_firmware_version(&self) -> Result<String> {
self.write_cmd(&[Command::Version as u8])?;
let mut buf = [0; 2];
self.read(&mut buf)?;
let num_bytes = u16::from_le_bytes(buf);
let mut buf = self.buf(num_bytes.into());
let mut buf = &mut buf[..usize::from(num_bytes)];
self.read(&mut buf)?;
Ok(String::from_utf8_lossy(
// The firmware version string returned may contain null bytes. If
// this happens, only return the preceding bytes.
match buf.iter().position(|&b| b == 0) {
Some(pos) => &buf[..pos],
None => buf,
},
)
.into_owned())
}
/// Reads the hardware version from the device.
///
/// This requires the probe to support [`Capability::GetHwVersion`].
pub fn read_hardware_version(&self) -> Result<HardwareVersion> {
self.require_capability(Capability::GetHwVersion)?;
self.write_cmd(&[Command::GetHwVersion as u8])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
Ok(HardwareVersion::from_u32(u32::from_le_bytes(buf)))
}
/// Reads the probe's communication speed information about the currently selected interface.
///
/// Supported speeds may differ between [`Interface`]s, so the right interface needs to be
/// selected for the returned value to make sense.
///
/// This requires the probe to support [`Capability::SpeedInfo`].
pub fn read_speeds(&self) -> Result<SpeedInfo> {
self.require_capability(Capability::SpeedInfo)?;
self.write_cmd(&[Command::GetSpeeds as u8])?;
let mut buf = [0; 6];
self.read(&mut buf)?;
let mut buf = &buf[..];
Ok(SpeedInfo {
base_freq: buf.read_u32::<LittleEndian>().unwrap(),
min_div: buf.read_u16::<LittleEndian>().unwrap(),
})
}
/// Reads the probe's SWO capture speed information.
///
/// This requires the probe to support [`Capability::Swo`].
pub fn read_swo_speeds(&self, mode: SwoMode) -> Result<SwoSpeedInfo> {
self.require_capability(Capability::Swo)?;
let mut buf = [0; 9];
buf[0] = Command::Swo as u8;
buf[1] = SwoCommand::GetSpeeds as u8;
buf[2] = 0x04; // Next param has 4 data Bytes
buf[3] = SwoParam::Mode as u8;
buf[4..8].copy_from_slice(&(mode as u32).to_le_bytes());
buf[8] = 0x00;
self.write_cmd(&buf)?;
let mut buf = [0; 28];
self.read(&mut buf)?;
let mut len = [0; 4];
len.copy_from_slice(&buf[0..4]);
let len = u32::from_le_bytes(len);
if len != 28 {
return Err(Error::new(
ErrorKind::Other,
format!("Unexpected response length {}, expected 28", len),
));
}
// Skip length and reserved word.
// FIXME: What's the word after the length for?
let mut buf = &buf[8..];
Ok(SwoSpeedInfo {
base_freq: buf.read_u32::<LittleEndian>().unwrap(),
min_div: buf.read_u32::<LittleEndian>().unwrap(),
max_div: buf.read_u32::<LittleEndian>().unwrap(),
min_presc: buf.read_u32::<LittleEndian>().unwrap(),
max_presc: buf.read_u32::<LittleEndian>().unwrap(),
})
}
/// Reads the maximum mem block size in Bytes.
///
/// This requires the probe to support [`Capability::GetMaxBlockSize`].
pub fn read_max_mem_block(&self) -> Result<u32> {
// This cap refers to a nonexistent command `GET_MAX_BLOCK_SIZE`, but it probably means
// `GET_MAX_MEM_BLOCK`.
self.require_capability(Capability::GetMaxBlockSize)?;
self.write_cmd(&[Command::GetMaxMemBlock as u8])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
/// Returns the capabilities advertised by the probe.
pub fn capabilities(&self) -> Capabilities {
self.caps
}
/// Returns the set of target interfaces supported by the probe.
pub fn available_interfaces(&self) -> Interfaces {
self.interfaces
}
/// Reads the currently selected target interface.
///
/// **Note**: There is no guarantee that the returned interface is actually supported (ie. it
/// might not be in the list returned by [`JayLink::available_interfaces`]). In particular, some
/// embedded J-Link probes start up with JTAG selected, but only support SWD.
pub fn current_interface(&self) -> Interface {
self.interface
}
/// Selects the interface to use for talking to the target MCU.
///
/// Switching interfaces will reset the configured transfer speed, so [`JayLink::set_speed`]
/// needs to be called *after* `select_interface`.
///
/// This requires the probe to support [`Capability::SelectIf`].
///
/// **Note**: Selecting a different interface may cause the J-Link to perform target I/O!
pub fn select_interface(&mut self, intf: Interface) -> Result<()> {
if self.interface == intf {
return Ok(());
}
self.require_capability(Capability::SelectIf)?;
self.require_interface_supported(intf)?;
self.write_cmd(&[Command::SelectIf as u8, intf.as_u8()])?;
// Returns the previous interface, ignore it
let mut buf = [0; 4];
self.read(&mut buf)?;
self.interface = intf;
Ok(())
}
/// Changes the state of the TMS / SWDIO pin (pin 7).
///
/// The pin will be set to the level of `VTref` if `tms` is `true`, and to GND if it is `false`.
///
/// **Note**: On some hardware, detaching `VTref` might not affect the internal reading, so the
/// old level might still be used afterwards.
pub fn set_tms(&mut self, tms: bool) -> Result<()> {
let cmd = if tms {
Command::HwTms1
} else {
Command::HwTms0
};
self.write_cmd(&[cmd as u8])
}
/// Changes the state of the TDI / TX pin (pin 5).
///
/// The pin will be set to the level of `VTref` if `tdi` is `true`, and to GND if it is `false`.
///
/// **Note**: On some hardware, detaching `VTref` might not affect the internal reading, so the
/// old level might still be used afterwards.
pub fn set_tdi(&mut self, tdi: bool) -> Result<()> {
let cmd = if tdi {
Command::HwData1
} else {
Command::HwData0
};
self.write_cmd(&[cmd as u8])
}
/// Changes the state of the (n)TRST pin (pin 3).
///
/// The pin will be set to the level of `VTref` if `trst` is `true`, and to GND if it is
/// `false`.
///
/// **Note**: On some hardware, detaching `VTref` might not affect the internal reading, so the
/// old level might still be used afterwards.
///
/// **Note**: Some embedded J-Link probes may not expose this pin or may not allow controlling
/// it using this function.
pub fn set_trst(&mut self, trst: bool) -> Result<()> {
let cmd = if trst {
Command::HwTrst1
} else {
Command::HwTrst0
};
self.write_cmd(&[cmd as u8])
}
/// Changes the state of the RESET pin (pin 15).
///
/// RESET is an open-collector / open-drain output. If `reset` is `true`, the output will float.
/// If `reset` is `false`, the output will be pulled to ground.
///
/// **Note**: Some embedded J-Link probes may not expose this pin or may not allow controlling
/// it using this function.
pub fn set_reset(&mut self, reset: bool) -> Result<()> {
let cmd = if reset {
Command::HwReset1
} else {
Command::HwReset0
};
self.write_cmd(&[cmd as u8])
}
/// Resets the target's JTAG TAP controller by temporarily asserting (n)TRST (Pin 3).
///
/// This might not do anything if the pin is not connected to the target. It does not affect
/// non-JTAG target interfaces.
pub fn reset_trst(&mut self) -> Result<()> {
self.write_cmd(&[Command::ResetTrst as u8])
}
/// Resets the target by temporarily asserting the RESET pin (pin 15).
///
/// This might not do anything if the RESET pin is not connected to the target.
pub fn reset_target(&mut self) -> Result<()> {
self.write_cmd(&[Command::ResetTarget as u8])
}
/// Sets the target communication speed.
///
/// If `speed` is set to [`SpeedConfig::ADAPTIVE`], then the probe has to support
/// [`Capability::AdaptiveClocking`]. Note that adaptive clocking may not work for all target
/// interfaces (eg. SWD).
///
/// When the selected target interface is switched (by calling [`JayLink::select_interface`], or
/// any API method that automatically selects an interface), the communication speed is reset to
/// some unspecified default value.
pub fn set_speed(&mut self, speed: SpeedConfig) -> Result<()> {
if speed.raw == SpeedConfig::ADAPTIVE.raw {
self.require_capability(Capability::AdaptiveClocking)?;
}
let mut buf = [Command::SetSpeed as u8, 0, 0];
buf[1..3].copy_from_slice(&speed.raw.to_le_bytes());
self.write_cmd(&buf)?;
Ok(())
}
/// Reads the target voltage measured on the `VTref` pin, in millivolts.
///
/// In order to use the J-Link, this voltage must be present, since it will be used as the level
/// of the I/O signals to the target.
pub fn read_target_voltage(&self) -> Result<u16> {
self.write_cmd(&[Command::GetState as u8])?;
let mut buf = [0; 8];
self.read(&mut buf)?;
let voltage = [buf[0], buf[1]];
Ok(u16::from_le_bytes(voltage))
}
/// Enables or disables the 5V Power supply on pin 19.
///
/// This requires the probe to support [`Capability::SetKsPower`].
///
/// **Note**: The startup state of the power supply can be configured in non-volatile memory.
///
/// **Note**: Some embedded J-Links may not provide this feature or do not have the 5V supply
/// routed to a pin. In that case this function might return an error, or it might return
/// successfully, but without doing anything.
///
/// **Note**: The 5V supply is protected against overcurrent. Check the device manual for more
/// information on this.
///
/// [`SET_KS_POWER`]: Capabilities::SET_KS_POWER
pub fn set_kickstart_power(&mut self, enable: bool) -> Result<()> {
self.require_capability(Capability::SetKsPower)?;
self.write_cmd(&[Command::SetKsPower as u8, enable as u8])?;
Ok(())
}
/// Performs a JTAG I/O operation.
///
/// This will shift out data on `TMS` (pin 7) and `TDI` (pin 5), while reading data shifted
/// into `TDO` (pin 13).
///
/// The data received on `TDO` is returned to the caller as an iterator yielding `bool`s.
///
/// The caller must ensure that the probe is in JTAG mode by calling
/// [`JayLink::select_interface`]`(`[`Interface::Jtag`]`)`.
///
/// # Parameters
///
/// * `tms`: TMS bits to transmit.
/// * `tdi`: TDI bits to transmit.
///
/// # Panics
///
/// This method will panic if `tms` and `tdi` have different lengths. It will also panic if any
/// of them contains more then 65535 bits of data, which is the maximum amount that can be
/// transferred in one operation.
///
// NB: Explicit `'a` lifetime used to improve rustdoc output
pub fn jtag_io<'a, M, D>(&'a mut self, tms: M, tdi: D) -> Result<BitIter<'a>>
where
M: IntoIterator<Item = bool>,
D: IntoIterator<Item = bool>,
{
self.require_interface_selected(Interface::Jtag)?;
let mut has_status_byte = false;
// There's 3 commands for doing a JTAG transfer. The older 2 are obsolete with hardware
// version 5 and above, which adds the 3rd command. Unfortunately we cannot reliably use the
// HW version to determine this since some embedded J-Link probes have a HW version of
// 1.0.0, but still support SWD, so we use the `SELECT_IF` capability instead.
let cmd = if self.capabilities().contains(Capability::SelectIf) {
// Use the new JTAG3 command, make sure to select the JTAG interface mode
self.select_interface(Interface::Jtag)?;
has_status_byte = true;
Command::HwJtag3
} else {
// Use the legacy JTAG2 command
// FIXME is HW_JTAG relevant at all?
Command::HwJtag2
};
// Collect the bit iterators into the buffer. We don't know the length in advance.
let tms = tms.into_iter();
let tdi = tdi.into_iter();
let bit_count_hint = cmp::max(tms.size_hint().0, tdi.size_hint().0);
let capacity = 1 + 1 + 2 + ((bit_count_hint + 7) / 8) * 2;
let mut buf = self.buf(capacity);
buf.resize(4, 0);
buf[0] = cmd as u8;
// buf[1] is dummy data for alignment
// buf[2..=3] is the bit count, which we'll fill in later
let mut tms_bit_count = 0;
buf.extend(tms.inspect(|_| tms_bit_count += 1).collapse_bytes());
let mut tdi_bit_count = 0;
buf.extend(tdi.inspect(|_| tdi_bit_count += 1).collapse_bytes());
assert_eq!(
tms_bit_count, tdi_bit_count,
"TMS and TDI must have the same number of bits"
);
let bit_count = u16::try_from(tms_bit_count).expect("too much data to transfer");
// JTAG3 and JTAG2 use the same format for JTAG operations
buf[2..=3].copy_from_slice(&bit_count.to_le_bytes());
self.write_cmd(&buf)?;
// Round bit count up to multple of 8 to get the number of response bytes.
let num_resp_bytes = (tms_bit_count + 7) >> 3;
trace!(
"{} TMS/TDI bits sent; reading {} response bytes",
tms_bit_count,
num_resp_bytes
);
// Response is `num_resp_bytes` TDO data bytes and one status byte,
// if the JTAG3 command is used.
let mut read_len = num_resp_bytes;
if has_status_byte {
read_len += 1;
}
self.read(&mut buf[..read_len])?;
// Check the status if a JTAG3 command was used.
if has_status_byte && buf[read_len - 1] != 0 {
return Err(Error::new(
ErrorKind::Other,
format!(
"probe I/O command returned error code {:#x}",
buf[read_len - 1]
),
));
}
drop(buf);
Ok(BitIter::new(
&self.cmd_buf.get_mut()[..num_resp_bytes],
tms_bit_count,
))
}
/// Performs an SWD I/O operation.
///
/// This requires the probe to support [`Capability::SelectIf`] and support for
/// [`Interface::Swd`].
///
/// The caller must ensure that the probe is in SWD mode by calling
/// [`JayLink::select_interface`]`(`[`Interface::Swd`]`)`.
///
/// # Parameters
///
/// * `dir`: Transfer directions of the `swdio` bits (`false` = 0 = Input, `true` = 1 = Output).
/// * `swdio`: SWD data bits.
///
/// If `dir` is `true`, the corresponding bit in `swdio` will be written to the target; if it is
/// `false`, the bit in `swdio` is ignored and a bit is read from the target instead.
///
/// # Return Value
///
/// An iterator over the `SWDIO` bits is returned. Bits that were sent to the target (where
/// `dir` = `true`) are undefined, and bits that were read from the target (`dir` = `false`)
/// will have whatever value the target sent.
// NB: Explicit `'a` lifetime used to improve rustdoc output
pub fn swd_io<'a, D, S>(&'a mut self, dir: D, swdio: S) -> Result<BitIter<'a>>
where
D: IntoIterator<Item = bool>,
S: IntoIterator<Item = bool>,
{
self.require_interface_selected(Interface::Swd)?;
// Collect the bit iterators into the buffer. We don't know the length in advance.
let dir = dir.into_iter();
let swdio = swdio.into_iter();
let bit_count_hint = cmp::max(dir.size_hint().0, swdio.size_hint().0);
let capacity = 1 + 1 + 2 + ((bit_count_hint + 7) / 8) * 2;
let mut buf = self.buf(capacity);
buf.resize(4, 0);
buf[0] = Command::HwJtag3 as u8;
buf[1] = 0;
// buf[1] is dummy data for alignment
// buf[2..=3] is the bit count, which we'll fill in later
let mut dir_bit_count = 0;
buf.extend(dir.inspect(|_| dir_bit_count += 1).collapse_bytes());
let mut swdio_bit_count = 0;
buf.extend(swdio.inspect(|_| swdio_bit_count += 1).collapse_bytes());
assert_eq!(
dir_bit_count, swdio_bit_count,
"`dir` and `swdio` must have the same number of bits"
);
assert!(dir_bit_count < 65535, "too much data to transfer");
let num_bits = dir_bit_count as u16;
buf[2..=3].copy_from_slice(&num_bits.to_le_bytes());
let num_bytes = usize::from((num_bits + 7) >> 3);
self.write_cmd(&buf)?;
// Response is `num_bytes` SWDIO data bytes and one status byte
self.read(&mut buf[..num_bytes + 1])?;
if buf[num_bytes] != 0 {
return Err(format!(
"probe I/O command returned error code {:#x}",
buf[num_bytes]
))
.jaylink_err();
}
drop(buf);
Ok(BitIter::new(
&self.cmd_buf.get_mut()[..num_bytes],
dir_bit_count,
))
}
/// Starts capturing SWO data.
///
/// This will switch the probe to SWD interface mode if necessary (required for SWO capture).
///
/// Requires the probe to support [`Capability::Swo`].
///
/// # Parameters
///
/// - `mode`: The SWO data encoding mode to use.
/// - `speed`: The data rate to capture at (when using [`SwoMode::Uart`], this is the UART baud
/// rate).
/// - `buf_size`: The size (in Bytes) of the on-device buffer to allocate for the SWO data. You
/// can call [`JayLink::read_max_mem_block`] to get an approximation of the available memory
/// on the probe.
///
/// # Return Value
///
/// This returns a [`SwoStream`] object, which can be used to directly read the captured SWO
/// data via [`std::io::Read`]. If blocking reads are undesired (or the [`JayLink`] instance
/// needs to be used for something else while SWO capture is in progress), the [`SwoStream`]
/// can be ignored and [`JayLink::swo_read`] be used instead.
pub fn swo_start<'a>(
&'a mut self,
mode: SwoMode,
speed: u32,
buf_size: u32,
) -> Result<SwoStream<'a>> {
self.require_capability(Capability::Swo)?;
// The probe must be in SWD mode for SWO capture to work.
self.require_interface_selected(Interface::Swd)?;
let mut buf = [0; 21];
buf[0] = Command::Swo as u8;
buf[1] = SwoCommand::Start as u8;
buf[2] = 0x04;
buf[3] = SwoParam::Mode as u8;
buf[4..8].copy_from_slice(&(mode as u32).to_le_bytes());
buf[8] = 0x04;
buf[9] = SwoParam::Baudrate as u8;
buf[10..14].copy_from_slice(&speed.to_le_bytes());
buf[14] = 0x04;
buf[15] = SwoParam::BufferSize as u8;
buf[16..20].copy_from_slice(&buf_size.to_le_bytes());
buf[20] = 0x00;
self.write_cmd(&buf)?;
let mut status = [0; 4];
self.read(&mut status)?;
let status = SwoStatus::new(u32::from_le_bytes(status));
Ok(SwoStream {
jaylink: self,
speed,
buf_size,
buf: Cursor::new(Vec::new()),
next_poll: Instant::now(),
status: Cell::new(status),
})
}
/// Stops capturing SWO data.
pub fn swo_stop(&mut self) -> Result<()> {
self.require_capability(Capability::Swo)?;
let buf = [
Command::Swo as u8,
SwoCommand::Stop as u8,
0x00, // no parameters
];
self.write_cmd(&buf)?;
let mut status = [0; 4];
self.read(&mut status)?;
let _status = SwoStatus::new(u32::from_le_bytes(status));
// FIXME: What to do with the status?
Ok(())
}
/// Reads captured SWO data from the probe and writes it to `data`.
///
/// This needs to be called regularly after SWO capturing has been started. If it is not called
/// often enough, the buffer on the probe will fill up and device data will be dropped. You can
/// call [`SwoData::did_overrun`] to check for this condition.
///
/// **Note**: the probe firmware seems to dislike many short SWO reads (as in, the probe will
/// *fall off the bus and reset*), so it is recommended to use a buffer that is the same size as
/// the on-probe data buffer.
pub fn swo_read<'a>(&self, data: &'a mut [u8]) -> Result<SwoData<'a>> {
let mut cmd = [0; 9];
cmd[0] = Command::Swo as u8;
cmd[1] = SwoCommand::Read as u8;
cmd[2] = 0x04;
cmd[3] = SwoParam::ReadSize as u8;
cmd[4..8].copy_from_slice(&(data.len() as u32).to_le_bytes());
cmd[8] = 0x00;
self.write_cmd(&cmd)?;
let mut header = [0; 8];
self.read(&mut header)?;
let status = {
let mut status = [0; 4];
status.copy_from_slice(&header[0..4]);
let bits = u32::from_le_bytes(status);
SwoStatus::new(bits)
};
let length = {
let mut length = [0; 4];
length.copy_from_slice(&header[4..8]);
u32::from_le_bytes(length)
};
if status.contains(SwoStatus::OVERRUN) {
warn!("SWO probe buffer overrun");
}
let len = length as usize;
let buf = &mut data[..len];
self.read(buf)?;
Ok(SwoData { data: buf, status })
}
}
impl fmt::Debug for JayLink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JayLink")
.field("manufacturer", &self.manufacturer)
.field("product", &self.product)
.field("serial", &self.serial)
.finish()
}
}
/// A SWO data stream that implements [`std::io::Read`].
///
/// This is one way to consume SWO data. The other is to call [`JayLink::swo_read`] after SWO
/// capturing has been started.
///
/// Reading from this stream will block until some data is captured by the probe.
#[derive(Debug)]
pub struct SwoStream<'a> {
jaylink: &'a JayLink,
speed: u32,
buf_size: u32,
next_poll: Instant,
/// Internal buffer the size of the on-probe buffer. This is filled in one go to avoid
/// performing small reads which may crash the probe.
buf: Cursor<Vec<u8>>,
/// Accumulated SWO errors.
status: Cell<SwoStatus>,
}
impl SwoStream<'_> {
/// Returns whether the probe-internal buffer overflowed at some point, and clears the flag.
///
/// This indicates that some device data was lost, and should be communicated to the end-user.
pub fn did_overrun(&self) -> bool {
let did = self.status.get().contains(SwoStatus::OVERRUN);
self.status.set(self.status.get() & !SwoStatus::OVERRUN);
did
}
/// Computes the suggested polling interval to avoid buffer overruns.
fn poll_interval(&self) -> Duration {
const MULTIPLIER: u32 = 2;
let bytes_per_sec = self.speed / 8;
let buffers_per_sec =
cmp::max(1, bytes_per_sec / self.buf.get_ref().len() as u32) * MULTIPLIER;
Duration::from_micros(1_000_000 / u64::from(buffers_per_sec))
}
}
fn to_io_error(error: Error) -> io::Error {
io::Error::new(io::ErrorKind::Other, error)
}
impl<'a> Read for SwoStream<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.buf.position() == self.buf.get_ref().len() as u64 {
// At end of buffer. (Blocking) Refill.
self.buf.get_mut().resize(self.buf_size as usize, 0);
loop {
// If we have recently polled, wait until the next poll is useful to avoid 100% CPU
// usage.
let now = Instant::now();
if now < self.next_poll {
thread::sleep(self.next_poll - now);
}
let buf = self.buf.get_mut();
let data = self.jaylink.swo_read(buf).map_err(to_io_error)?;
self.status.set(self.status.get() | data.status);
let len = data.len();
// Since `self.buf` is the same length as the on-probe buffer, the probe buffer is
// now empty and we can wait `self.poll_interval()` until the next read.
self.next_poll += self.poll_interval();
if len != 0 {
// There's now *some* data in the buffer.
self.buf.get_mut().truncate(len);
self.buf.set_position(0);
break;
}
// If `data.len() == 0`, no data from the target has arrived. Since we can't return 0
// bytes (it indicates the end of the stream, in reality the stream is just very slow),
// we just loop (and sleep appropriately to not waste CPU).
}
}
self.buf.read(buf)
}
}
/// SWO data that was read via [`JayLink::swo_read`].
#[derive(Debug)]
pub struct SwoData<'a> {
data: &'a [u8],
status: SwoStatus,
}
impl<'a> SwoData<'a> {
/// Returns whether the probe-internal buffer overflowed before the last read.
///
/// This indicates that some device data was lost.
pub fn did_overrun(&self) -> bool {
self.status.contains(SwoStatus::OVERRUN)
}
}
impl<'a> AsRef<[u8]> for SwoData<'a> {
fn as_ref(&self) -> &[u8] {
self.data
}
}
impl<'a> Deref for SwoData<'a> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.data
}
}
/// A hardware version returned by [`JayLink::read_hardware_version`].
///
/// Note that the reported hardware version does not allow reliable feature detection, since
/// embedded J-Link probes might return a hardware version of 1.0.0 despite supporting SWD and other
/// much newer features.
#[derive(Debug)]
pub struct HardwareVersion(u32);
impl HardwareVersion {
fn from_u32(raw: u32) -> Self {
HardwareVersion(raw)
}
/// Returns the type of hardware (or `None` if the hardware type is unknown).
pub fn hardware_type(&self) -> Option<HardwareType> {
Some(match (self.0 / 1000000) % 100 {
0 => HardwareType::JLink,
1 => HardwareType::JTrace,
2 => HardwareType::Flasher,
3 => HardwareType::JLinkPro,
_ => return None,
})
}
/// The major version.
pub fn major(&self) -> u8 {
// Decimal coded Decimal, cool cool
(self.0 / 10000) as u8
}
/// The minor version.
pub fn minor(&self) -> u8 {
((self.0 % 10000) / 100) as u8
}
/// The hardware revision.
pub fn revision(&self) -> u8 {
(self.0 % 100) as u8
}
}
impl fmt::Display for HardwareVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(hw) = self.hardware_type() {
write!(f, "{} ", hw)?;
}
write!(f, "{}.{}.{}", self.major(), self.minor(), self.revision())
}
}
/// The hardware/product type of the device.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum HardwareType {
JLink,
JTrace,
Flasher,
JLinkPro,
}
impl fmt::Display for HardwareType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
HardwareType::JLink => "J-Link",
HardwareType::JTrace => "J-Trace",
HardwareType::Flasher => "J-Flash",
HardwareType::JLinkPro => "J-Link Pro",
})
}
}
/// J-Link communication speed info.
#[derive(Debug)]
pub struct SpeedInfo {
base_freq: u32,
min_div: u16,
}
impl SpeedInfo {
/// Returns the maximum supported speed for target communication (in Hz).
pub fn max_speed_hz(&self) -> u32 {
self.base_freq / u32::from(self.min_div)
}
/// Returns a `SpeedConfig` that configures the fastest supported speed.
pub fn max_speed_config(&self) -> SpeedConfig {
let khz = cmp::min(self.max_speed_hz() / 1000, 0xFFFE);
SpeedConfig::khz(khz.try_into().unwrap()).unwrap()
}
}
/// Supported SWO capture speed info.
#[derive(Debug)]
pub struct SwoSpeedInfo {
base_freq: u32,
min_div: u32,
#[allow(dead_code)]
max_div: u32,
min_presc: u32,
#[allow(dead_code)]
max_presc: u32,
}
impl SwoSpeedInfo {
/// Returns the maximum supported speed for SWO capture (in Hz).
pub fn max_speed_hz(&self) -> u32 {
self.base_freq / self.min_div / cmp::max(1, self.min_presc)
}
}
/// Target communication speed setting.
///
/// This determines the clock frequency of the target communication. Supported speeds for the
/// currently selected target interface can be fetched via [`JayLink::read_speeds`].
#[derive(Debug, Copy, Clone)]
pub struct SpeedConfig {
raw: u16,
}
impl SpeedConfig {
/// Let the J-Link probe decide the speed.
///
/// Requires the probe to support [`Capability::AdaptiveClocking`].
pub const ADAPTIVE: Self = Self { raw: 0xFFFF };
/// Manually specify speed in kHz.
///
/// Returns `None` if the value is the invalid value `0xFFFF`. Note that this doesn't mean that
/// every other value will be accepted by the device.
pub fn khz(khz: u16) -> Option<Self> {
if khz == 0xFFFF {
None
} else {
Some(Self { raw: khz })
}
}
}
impl fmt::Display for SpeedConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.raw == Self::ADAPTIVE.raw {
f.write_str("adaptive")
} else {
write!(f, "{} kHz", self.raw)
}
}
}
/// Generic info about a USB device.
///
/// Returned by [`scan_usb`].
#[derive(Debug)]
pub struct UsbDeviceInfo {
inner: rusb::Device<rusb::GlobalContext>,
vid: u16,
pid: u16,
}
impl UsbDeviceInfo {
/// Returns the vendor ID.
///
/// Vendor IDs are centrally registered and can be looked up for example at
/// [http://www.linux-usb.org/usb.ids](http://www.linux-usb.org/usb.ids).
pub fn vid(&self) -> u16 {
self.vid
}
/// Returns the product ID.
pub fn pid(&self) -> u16 {
self.pid
}
/// Returns the bus this device is attached to.
pub fn bus_number(&self) -> u8 {
self.inner.bus_number()
}
/// Returns the device address on the bus it's attached to.
pub fn address(&self) -> u8 {
self.inner.address()
}
/// Returns the port the device is attached to.
pub fn port_number(&self) -> u8 {
self.inner.port_number()
}
/// Tries to open this USB device.
///
/// If successful, returns a [`JayLink`] instance.
///
/// This method is equivalent to [`JayLink::open_usb`].
pub fn open(self) -> Result<JayLink> {
JayLink::open_usb(self)
}
}
/// Scans for J-Link USB devices.
///
/// The returned iterator will yield all devices made by Segger, without filtering the product ID.
pub fn scan_usb() -> Result<impl Iterator<Item = UsbDeviceInfo>> {
log_libusb_info();
Ok(rusb::devices()
.jaylink_err()?
.iter()
.filter_map(|dev| {
// This calls `libusb_get_device_descriptor`, which should be unable to fail in any
// libusb version (it only accesses cached descriptor data).
let descr = dev
.device_descriptor()
.expect("libusb_get_device_descriptor returned unexpected error");
if descr.vendor_id() == VID_SEGGER {
Some(UsbDeviceInfo {
vid: descr.vendor_id(),
pid: descr.product_id(),
inner: dev,
})
} else {
None
}
})
.collect::<Vec<_>>()
.into_iter())
}
fn log_libusb_info() {
static DID_LOG: AtomicBool = AtomicBool::new(false);
if DID_LOG.swap(true, Ordering::Acquire) {
return;
}
let vers = rusb::version();
debug!(
"libusb {}.{}.{}.{}{}",
vers.major(),
vers.minor(),
vers.micro(),
vers.nano(),
vers.rc().map(|rc| format!("-{}", rc)).unwrap_or_default(),
);
debug!("libusb has capability API: {:?}", rusb::has_capability());
debug!("libusb has HID access: {:?}", rusb::has_hid_access());
debug!("libusb has hotplug support: {:?}", rusb::has_hotplug());
debug!(
"libusb can detach kernel driver: {:?}",
rusb::supports_detach_kernel_driver()
);
}