Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions pylink/jlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -2629,6 +2629,201 @@ def jtag_flush(self):
"""
self._dll.JLINKARM_WriteBits()

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_store_instruction(self, instr, ir_len):
"""Stores the specified JTAG instruction in the internal output buffer to
be written to the instruction register of the JTAG device.

The necessary bits to place the TAP controller into the Shift-IR state
are automatically added in order to form the complete command
sequence for the given instruction.

Data in the output buffer is not flushed until TDO data is required, or
``jtag_sync_bits()`` or ``jtag_sync_bytes()`` is called.

Args:
self (JLink): the ``JLink`` instance.
instr (int): JTAG protocol command bits.
ir_len (int): instruction register length.

Returns:
Bit position in input buffer after instruction transmission.
"""
buf = ctypes.c_uint8(instr)
return self._dll.JLINKARM_JTAG_StoreInst(ctypes.byref(buf), ir_len)

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_store_data(self, data, dr_len):
"""Stores the specified JTAG data in the internal output buffer to be
written to the data register of the JTAG device.

The necessary bits to place the TAP controller into the Shift-DR state
are automatically added in order to form a complete data transmission.

Data in the output buffer is not flushed until TDO data is required, or
``jtag_sync_bits()`` or ``jtag_sync_bytes()`` is called.

Args:
self (JLink): the ``JLink`` instance.
data (list): list of bits to transfer.
dr_len (int): data register length.

Returns:
Bit position in input buffer after instruction transmission.

Raises:
TypeError: If passed data is not bytes or a list of integers.
"""
buf = data
if isinstance(buf, list):
Copy link

@ksigurdsson ksigurdsson Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone didn't read the doctstring and simply passed in an int for data then this if statement wouldn't execute and then the subsequent call to self._dll.JLINKARM_JTAG_StoreData would fail because you can't call len() on an int.

Is it worth adding an else to the if isinstance(buf, list): to raise if buf is not a list?

buf = bytes(buf)
elif not any(isinstance(buf, t) for t in [bytes, bytearray]):
raise TypeError('Expected to be given bytes / list: given %s' % type(buf))

return self._dll.JLINKARM_JTAG_StoreData(buf, len(data) * dr_len)

@interface_required(enums.JLinkInterfaces.JTAG)
@connection_required
def jtag_get_device_info(self, index=0):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function takes index as an input to determine which TAP on the JTAG chain to query - this makes perfect sense to me. How do the jtag_store_instruction and jtag_store_data functions determine which JTAG TAP to access? I.e. if I have a JTAG chain with, say, three taps on it how do I specify which tap to access?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would have to use jtag_configure():

# Open connection.
jl.open()

# Total length of all instruction registers before the desired JTAG device.
# (e.g. 4 if selecting the second device and length of the IR of the previous device is 4).
ir_len = ...

# Total number of data register bits from devices that are between the
# JTAG controller's TDI pin and the JTAG device you're trying to access.
dr_len = ...

# Select the device based on the IR and DR lengths.
jl.jtag_configure(ir_len, dr_len)

# Will return the device information for the selected device.
jl.jtag_get_device_info(...)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK - fantastic - that makes sense. Thanks

"""Retrieves the JTAG related information for the JTAG device on the scan chain.

Args:
self (JLink): the ``JLink`` instance.
index (int): index of the device on the scan chain.

Returns:
A ``JLinkJTAGDeviceInfo`` describing the requested device.

Raises:
ValueError: if index is less than 0 or >= number of devices on the scan chain.
"""
if index < 0:
raise ValueError('Invalid index provided, must be > 0.')

info = structs.JLinkJTAGDeviceInfo()
res = self._dll.JLINKARM_JTAG_GetDeviceInfo(index, ctypes.byref(info))
if res == -1:
raise ValueError('Invalid index provided, no device found.')

info.DeviceId = self._dll.JLINKARM_JTAG_GetDeviceId(index)
return info

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_read(self, offset, num_bits):
"""Reads the specified number of bits from the JTAG input buffer.

Note:
If there is data in the output buffer, then ``num_bits`` of data will
be transmitted.

Args:
self (JLink): the ``JLink`` instance.
offset (int): bit position within the input buffer to read from.
num_bits (int): total number of bits to read.

Returns:
List of bytes containing the TDO data. This function may return more
bytes than expected due to no context around the data size. The
caller should pull bits as appopriate starting from the first returned
byte.
"""
# The smallest data length is 4 bits, so we use that as a divider. If
# the data length is actually 7 and the user specifies 7, we will
# return two integers, but that is fine, so the caller ultimately knows
# the data length they need.
buf_size = num_bits // 4
if (num_bits % 4) > 0:
buf_size += 1
buf = (ctypes.c_uint8 * buf_size)()
self._dll.JLINKARM_JTAG_GetData(ctypes.byref(buf), offset, num_bits)
return list(buf)

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_read8(self, offset):
"""Reads a 8-bit integer from the JTAG input buffer.

Note:
If there is data in the output buffer, this function will force a
transmission.

Args:
self (JLink): the ``JLink`` instance.
offset (int): bit position within the input buffer to read from.

Returns:
The read 8-bit integer from the input buffer.
"""
return self._dll.JLINKARM_JTAG_GetU8(offset)

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_read16(self, offset):
"""Reads a 16-bit integer from the JTAG input buffer.

Note:
If there is data in the output buffer, this function will force a
transmission.

Args:
self (JLink): the ``JLink`` instance.
offset (int): bit position within the input buffer to read from.

Returns:
The read 16-bit integer from the input buffer.
"""
return self._dll.JLINKARM_JTAG_GetU16(offset)

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_read32(self, offset):
"""Reads a 32-bit integer from the JTAG input buffer.

Note:
If there is data in the output buffer, this function will force a
transmission.

Args:
self (JLink): the ``JLink`` instance.
offset (int): bit position within the input buffer to read from.

Returns:
The read 32-bit integer from the input buffer.
"""
return self._dll.JLINKARM_JTAG_GetU32(offset)

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_sync_bits(self):
"""Flushes the internal output buffer to the JTAG device.

Args:
self (JLink): the ``JLink`` instance.

Returns:
``None``
"""
self._dll.JLINKARM_JTAG_SyncBits()

@interface_required(enums.JLinkInterfaces.JTAG)
@open_required
def jtag_sync_bytes(self):
"""Flushes the data content in the internal output buffer to the JTAG device.

This function will add the necessary bits to ensure the transmitted
data is byte-aligned.

Args:
self (JLink): the ``JLink`` instance.

Returns:
``None``
"""
self._dll.JLINKARM_JTAG_SyncBytes()

@interface_required(enums.JLinkInterfaces.SWD)
@connection_required
def swd_read8(self, offset):
Expand Down
51 changes: 51 additions & 0 deletions pylink/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1468,3 +1468,54 @@ def __str__(self):
String formatted instance.
"""
return '%s(SampleFreq=%uHz, MinDiv=%u)' % (self.__class__.__name__, self.BaseSampleFreq, self.MinDiv)


class JLinkJTAGDeviceInfo(ctypes.Structure):
"""Structure representing the information of a device on the JTAG scan chain.

Attributes:
sName: the name of the device.
IRLen: instruction register length.
IRPrint: instruction register print.
DeviceId: JTAG id.
"""
_fields_ = [
('sName', ctypes.c_char_p),
('IRLen', ctypes.c_uint32),
('IRPrint', ctypes.c_uint32),
('DeviceId', ctypes.c_uint32)
]

def __repr__(self):
"""Returns a representation of this instance.

Args:
self (JLinkJTAGDeviceInfo): the ``JLinkJTAGDeviceInfo`` instance

Returns:
Returns a string representation of the instance.
"""
return 'JLinkJTAGDeviceInfo(%s)' % self.__str__()

def __str__(self):
"""Returns a string representation of this instance.

Args:
self (JLinkJTAGDeviceInfo): the ``JLinkJTAGDeviceInfo`` instance

Returns:
Returns a string specifying the device name and ID.
"""
return '%s <Device Id. %s>' % (self.name, self.DeviceId)

@property
def name(self):
"""Returns the name of the JTAG device.

Args:
self (JLinkJTAGDeviceInfo): the ``JLinkJTAGDeviceInfo`` instance

Returns:
Device name.
"""
return ctypes.cast(self.sName, ctypes.c_char_p).value.decode()
106 changes: 106 additions & 0 deletions tests/unit/test_jlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -3297,6 +3297,112 @@ def test_jlink_jtag_flush(self):
self.jlink.jtag_flush()
self.dll.JLINKARM_WriteBits.assert_called_once()

def test_jlink_jtag_store_instruction(self):
"""Tests the J-Link JTAG method for storing a JTAG instruction.

Args:
self (TestJLink): the ``TestJLink`` instance

Returns:
``None``
"""
cmd = 0xE
self.jlink.jtag_store_instruction(cmd, 4)

c_byte, num_bits = self.dll.JLINKARM_JTAG_StoreInst.call_args[0]
self.assertEqual(4, num_bits)

c_uint = ctypes.cast(c_byte, ctypes.POINTER(ctypes.c_uint8)).contents
self.assertEqual(cmd, c_uint.value)

def test_jlink_jtag_store_data(self):
"""Tests the J-Link JTAG method for storing TDI.

Args:
self (TestJLink): the ``TestJLink`` instance

Returns:
``None``
"""
tdi = [0xA, 0x3]
self.jlink.jtag_store_data(tdi, 5)

buf, num_bits = self.dll.JLINKARM_JTAG_StoreData.call_args[0]
expected_num_bits = len(tdi) * 5
self.assertEqual(expected_num_bits, num_bits)
self.assertEqual(b'\x0A\x03', bytearray(buf))

def test_jlink_jtag_get_device_info(self):
"""Tests the J-Link JTAG method for retrieving JTAG device information.

Args:
self (TestJLink): the ``TestJLink`` instance

Return:
``None``
"""
with self.assertRaises(ValueError):
_ = self.jlink.jtag_get_device_info(-1)

self.dll.JLINKARM_JTAG_GetDeviceInfo.return_value = -1
with self.assertRaises(ValueError):
_ = self.jlink.jtag_get_device_info(0)

def _get_device_info(index, info):
c_info = ctypes.cast(info, ctypes.POINTER(structs.JLinkJTAGDeviceInfo)).contents
c_info.IRLen = 0x1
c_info.IRPrint = 0x2
c_info.DeviceId = 0x1337
name = b"Silk Song"
c_info.sName = ctypes.cast(name, ctypes.c_char_p)
return 0

self.dll.JLINKARM_JTAG_GetDeviceInfo = _get_device_info
self.dll.JLINKARM_JTAG_GetDeviceId.return_value = 0x1337

info = self.jlink.jtag_get_device_info(0)
self.assertEqual(0x1337, info.DeviceId)
self.assertEqual(0x1, info.IRLen)
self.assertEqual(0x2, info.IRPrint)
self.assertEqual("Silk Song", info.name)
Copy link

@ksigurdsson ksigurdsson Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should info.name actually be info.sName? @hkpeprah

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.name is the property that converts the ctype string pointer to a Python string.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yes - got it. Thanks


def test_jlink_jtag_read(self):
"""Tests the J-Link JTAG read methods.

Args:
self (TestJLink): the ``TestJLink`` instance

Returns:
``None``
"""
self.jlink._tif = enums.JLinkInterfaces.JTAG

val = 0x12345678
self.dll.JLINKARM_JTAG_GetU8.return_value = val & 0xFF
self.dll.JLINKARM_JTAG_GetU16.return_value = val & 0xFFFF
self.dll.JLINKARM_JTAG_GetU32.return_value = val & 0xFFFFFFFF

self.assertEqual(0x78, self.jlink.jtag_read8(0))
self.assertEqual(0x5678, self.jlink.jtag_read16(0))
self.assertEqual(0x12345678, self.jlink.jtag_read32(0))

def _get_data(buf, offset, num_bits):
c_buf = ctypes.cast(buf, ctypes.POINTER(ctypes.c_uint8))
buf_index = 0
bit_index = 0
while num_bits:
rd_size = min(num_bits, 4)
num_bits -= rd_size
b = 0
for i in range(0, rd_size):
b |= ((val & (0x1 << bit_index)) >> bit_index) << i
bit_index += 1
c_buf[buf_index] = b
buf_index += 1

self.dll.JLINKARM_JTAG_GetData = _get_data
self.assertEqual([0x8, 0x7, 0x6, 0x5], self.jlink.jtag_read(0, 16))

def test_jlink_swd_read8(self):
"""Tests the J-Link ``swd_read8()`` method.

Expand Down
Loading