#![doc(html_root_url = "https://docs.rs/jaylink/0.1.1")]
#![doc(test(attr(deny(unused_imports, unused_must_use))))]
#![warn(missing_debug_implementations, rust_2018_idioms)]
mod bits;
mod capabilities;
mod error;
mod readme;
mod private {
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Private {}
}
pub use self::bits::BitIter;
pub use self::capabilities::Capabilities;
pub use self::error::{Error, ErrorKind};
use self::bits::IteratorExt as _;
use self::error::ResultExt as _;
use bitflags::bitflags;
use byteorder::{LittleEndian, ReadBytesExt};
use log::{debug, trace, warn};
use std::cell::{Cell, RefCell, RefMut};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use std::{cmp, fmt};
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,
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,
}
pub struct JayLink {
handle: rusb::DeviceHandle<rusb::GlobalContext>,
read_ep: u8,
write_ep: u8,
cmd_buf: RefCell<Vec<u8>>,
caps: Cell<Option<Capabilities>>,
interface: Cell<Option<Interface>>,
speed: Cell<Option<CommunicationSpeed>>,
manufacturer: String,
product: String,
serial: String,
}
impl JayLink {
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)
}
pub fn open_usb(usb_device: UsbDeviceInfo) -> Result<Self> {
let descr = usb_device
.inner
.device_descriptor()
.expect("libusb_get_device_descriptor returned unexpected error");
let mut handle = usb_device
.inner
.open()
.jaylink_err_while("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")?;
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);
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")?;
let conf = handle.active_configuration().jaylink_err()?;
if conf != 1 {
return Err("another application is accessing the device".to_string()).jaylink_err();
}
Ok(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: Cell::new(None),
interface: Cell::new(None),
speed: Cell::new(None),
handle,
})
}
pub fn manufacturer_string(&self) -> &str {
&self.manufacturer
}
pub fn product_string(&self) -> &str {
&self.product
}
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_capabilities(&self, cap: Capabilities) -> Result<()> {
let caps = self.read_capabilities()?;
if caps.contains(cap) {
Ok(())
} else {
Err(Error::new(
ErrorKind::MissingCapability,
format!("device is missing capabilities ({:?}) for operation", cap),
))
}
}
fn has_capabilities(&self, cap: Capabilities) -> Result<bool> {
let caps = self.read_capabilities()?;
Ok(caps.contains(cap))
}
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(buf).to_string())
}
pub fn read_hardware_version(&self) -> Result<HardwareVersion> {
self.require_capabilities(Capabilities::GET_HW_VERSION)?;
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)))
}
pub fn read_speeds(&self) -> Result<Speeds> {
self.require_capabilities(Capabilities::SPEED_INFO)?;
self.write_cmd(&[Command::GetSpeeds as u8])?;
let mut buf = [0; 6];
self.read(&mut buf)?;
let mut buf = &buf[..];
Ok(Speeds {
base_freq: buf.read_u32::<LittleEndian>().unwrap(),
min_div: buf.read_u16::<LittleEndian>().unwrap(),
})
}
pub fn read_max_mem_block(&self) -> Result<u32> {
self.require_capabilities(Capabilities::GET_MAX_BLOCK_SIZE)?;
self.write_cmd(&[Command::GetMaxMemBlock as u8])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
pub fn read_capabilities(&self) -> Result<Capabilities> {
if let Some(caps) = self.caps.get() {
Ok(caps)
} else {
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 caps.contains(Capabilities::GET_CAPS_EX) {
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(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.set(Some(caps));
Ok(caps)
}
}
pub fn set_tms(&mut self, tms: bool) -> Result<()> {
let cmd = if tms {
Command::HwTms1
} else {
Command::HwTms0
};
self.write_cmd(&[cmd as u8])
}
pub fn set_tdi(&mut self, tdi: bool) -> Result<()> {
let cmd = if tdi {
Command::HwData1
} else {
Command::HwData0
};
self.write_cmd(&[cmd as u8])
}
pub fn set_trst(&mut self, trst: bool) -> Result<()> {
let cmd = if trst {
Command::HwTrst1
} else {
Command::HwTrst0
};
self.write_cmd(&[cmd as u8])
}
pub fn set_reset(&mut self, reset: bool) -> Result<()> {
let cmd = if reset {
Command::HwReset1
} else {
Command::HwReset0
};
self.write_cmd(&[cmd as u8])
}
pub fn reset_trst(&mut self) -> Result<()> {
self.write_cmd(&[Command::ResetTrst as u8])
}
pub fn reset_target(&mut self) -> Result<()> {
self.write_cmd(&[Command::ResetTarget as u8])
}
pub fn read_current_interface(&self) -> Result<Interface> {
if let Some(intf) = self.interface.get() {
Ok(intf)
} else {
self.require_capabilities(Capabilities::SELECT_IF)?;
self.write_cmd(&[Command::SelectIf as u8, 0xFE])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
let raw = u32::from_le_bytes(buf);
let intf = Interface::from_u32(raw)
.ok_or_else(|| format!("invalid interface value {}", raw))
.jaylink_err()?;
debug!("read active interface: {:?}", intf);
self.interface.set(Some(intf));
Ok(intf)
}
}
pub fn read_available_interfaces(&self) -> Result<impl Iterator<Item = Interface>> {
self.require_capabilities(Capabilities::SELECT_IF)?;
self.write_cmd(&[Command::SelectIf as u8, 0xFF])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
let intfs = Interfaces::from_bits_truncate(u32::from_le_bytes(buf));
Ok(intfs.into_iter())
}
pub fn select_interface(&mut self, intf: Interface) -> Result<()> {
if self.interface.get() == Some(intf) {
return Ok(());
}
self.require_capabilities(Capabilities::SELECT_IF)?;
self.write_cmd(&[Command::SelectIf as u8, intf.as_u8()])?;
let mut buf = [0; 4];
self.read(&mut buf)?;
self.interface.set(Some(intf));
if let Some(speed) = self.speed.get() {
self.set_speed(speed)?;
}
Ok(())
}
pub fn set_speed(&mut self, speed: CommunicationSpeed) -> Result<()> {
if speed.raw == CommunicationSpeed::ADAPTIVE.raw {
self.require_capabilities(Capabilities::ADAPTIVE_CLOCKING)?;
}
let mut buf = [Command::SetSpeed as u8, 0, 0];
buf[1..3].copy_from_slice(&speed.raw.to_le_bytes());
self.write_cmd(&buf)?;
self.speed.set(Some(speed));
Ok(())
}
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))
}
pub fn set_kickstart_power(&mut self, enable: bool) -> Result<()> {
self.require_capabilities(Capabilities::SET_KS_POWER)?;
self.write_cmd(&[Command::SetKsPower as u8, enable as u8])?;
Ok(())
}
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>,
{
let mut has_status_byte = false;
let cmd = if self.has_capabilities(Capabilities::SELECT_IF)? {
self.select_interface(Interface::Jtag)?;
has_status_byte = true;
Command::HwJtag3
} else {
Command::HwJtag2
};
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.extend(tms.collapse_bytes());
let tms_byte_count = buf.len() - 4;
buf.extend(tdi.collapse_bytes());
let tdi_byte_count = buf.len() - 4 - tms_byte_count;
assert_eq!(
tms_byte_count, tdi_byte_count,
"TMS and TDI must have the same number of bits"
);
assert!((tms_byte_count * 8) < 65535, "too much data to transfer");
let num_bits = (tms_byte_count * 8) 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)?;
trace!("Reading {} antwort bytes", num_bytes);
let mut read_len = num_bytes;
if has_status_byte {
read_len += 1;
}
self.read(&mut buf[..read_len])?;
if has_status_byte && buf[read_len - 1] != 0 {
return Err(Error::new(ErrorKind::Other, "Error reading JTAG data"));
}
drop(buf);
Ok(BitIter::new(
&self.cmd_buf.get_mut()[..num_bytes],
num_bits as usize,
))
}
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.select_interface(Interface::Swd)?;
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;
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)?;
self.read(&mut buf[..num_bytes + 1])?;
if buf[num_bytes] != 0 {
return Err(format!("SWD op returned error code {:#x}", buf[num_bytes])).jaylink_err();
}
drop(buf);
Ok(BitIter::new(
&self.cmd_buf.get_mut()[..num_bytes],
dir_bit_count,
))
}
}
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()
}
}
#[derive(Debug, Copy, Clone)]
pub struct CommunicationSpeed {
raw: u16,
}
impl CommunicationSpeed {
pub const ADAPTIVE: Self = Self { raw: 0xFFFF };
pub fn khz(khz: u16) -> Option<Self> {
if khz == 0xFFFF {
None
} else {
Some(Self { raw: khz })
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Interface {
Jtag,
Swd,
#[doc(hidden)]
__NonExhaustive(private::Private),
}
impl Interface {
fn from_u32(raw: u32) -> Option<Self> {
match raw {
0 => Some(Interface::Jtag),
1 => Some(Interface::Swd),
_ => None,
}
}
fn as_u8(self) -> u8 {
match self {
Interface::Jtag => 0,
Interface::Swd => 1,
Interface::__NonExhaustive(_) => unreachable!(),
}
}
}
impl fmt::Display for Interface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Interface::Jtag => f.write_str("JTAG"),
Interface::Swd => f.write_str("SWD"),
Interface::__NonExhaustive(_) => unreachable!(),
}
}
}
bitflags! {
pub struct Interfaces: u32 {
const JTAG = (1 << 0);
const SWD = (1 << 1);
}
}
impl Interfaces {
pub fn into_iter(self) -> impl Iterator<Item = Interface> {
[(Self::JTAG, Interface::Jtag), (Self::SWD, Interface::Swd)]
.iter()
.filter(move |(flag, _)| self.contains(*flag))
.map(|(_, intf)| *intf)
}
}
#[derive(Debug)]
pub struct HardwareVersion(u32);
impl HardwareVersion {
fn from_u32(raw: u32) -> Self {
HardwareVersion(raw)
}
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,
})
}
pub fn major(&self) -> u8 {
(self.0 / 10000) as u8
}
pub fn minor(&self) -> u8 {
((self.0 % 10000) / 100) as u8
}
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())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum HardwareType {
JLink,
JTrace,
Flasher,
JLinkPro,
#[doc(hidden)]
__NonExhaustive(private::Private),
}
#[derive(Debug)]
pub struct Speeds {
base_freq: u32,
min_div: u16,
}
impl Speeds {
pub fn base_freq(&self) -> u32 {
self.base_freq
}
pub fn min_div(&self) -> u16 {
self.min_div
}
}
#[derive(Debug)]
pub struct UsbDeviceInfo {
inner: rusb::Device<rusb::GlobalContext>,
vid: u16,
pid: u16,
}
impl UsbDeviceInfo {
pub fn vid(&self) -> u16 {
self.vid
}
pub fn pid(&self) -> u16 {
self.pid
}
pub fn bus_number(&self) -> u8 {
self.inner.bus_number()
}
pub fn address(&self) -> u8 {
self.inner.address()
}
pub fn port_number(&self) -> u8 {
self.inner.port_number()
}
pub fn open(self) -> Result<JayLink> {
JayLink::open_usb(self)
}
}
pub fn scan_usb() -> Result<impl Iterator<Item = UsbDeviceInfo>> {
log_libusb_info();
Ok(rusb::devices()
.jaylink_err()?
.iter()
.filter_map(|dev| {
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(String::new())
);
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()
);
}