Initial commit

This commit is contained in:
wagiminator
2022-09-11 11:42:08 +02:00
parent 54d0f15ce8
commit 03e8a184a2
166 changed files with 86898 additions and 2 deletions

View File

@@ -0,0 +1,144 @@
"""
CMSIS-DAP wrapper for custom commands (using vendor extensions)
This mechanism is used to pass JTAGICE3-style commands for AVR devices
over the CMSIS-DAP interface
"""
import time
from logging import getLogger
from ..util.binary import unpack_be16
from ..util import print_helpers
from .cmsisdap import CmsisDapUnit
class AvrCommandError(Exception):
"""
Exception type for AVR command-response wrapping
"""
pass
class AvrCommand(CmsisDapUnit):
"""
Wraps AVR command and responses
"""
# Vendor Commands used to transport AVR over CMSIS-DAP
AVR_COMMAND = 0x80
AVR_RESPONSE = 0x81
AVR_EVENT = 0x82
AVR_MORE_FRAGMENTS = 0x00
AVR_FINAL_FRAGMENT = 0x01
# Retry delay on AVR receive frame
AVR_RETRY_DELAY_MS = 50
def __init__(self, transport, no_timeouts=False):
self.no_timeouts = no_timeouts
self.timeout = 1000
CmsisDapUnit.__init__(self, transport)
self.ep_size = transport.get_report_size()
self.logger = getLogger(__name__)
self.logger.debug("Created AVR command on DAP wrapper")
def poll_events(self):
"""
Polling for events from AVRs
:return: response from events
"""
self.logger.debug("Polling AVR events")
resp = self.dap_command_response(bytearray([self.AVR_EVENT]))
return resp
def _avr_response_receive_frame(self):
retries = int(self.timeout / self.AVR_RETRY_DELAY_MS)
# Get the delay in seconds
delay = self.AVR_RETRY_DELAY_MS / 1000
while retries or self.no_timeouts:
resp = self.dap_command_response(bytearray([self.AVR_RESPONSE]))
if resp[0] != self.AVR_RESPONSE:
# Response received is not valid. Abort.
raise AvrCommandError("AVR response DAP command failed; invalid token: 0x{:02X}".format(resp[0]))
if resp[1] != 0x00:
return resp
self.logger.debug("Resp: %s", print_helpers.bytelist_to_hex_string(resp))
# Delay in seconds
time.sleep(delay)
retries -= 1
raise AvrCommandError("AVR response timeout")
# Chops command up into fragments
def _fragment_command_packet(self, command_packet):
packets_total = int((len(command_packet) / (self.ep_size - 4)) + 1)
self.logger.debug("Fragmenting AVR command into {:d} chunks".format(packets_total))
fragments = []
for i in range(0, packets_total):
command_fragment = bytearray([self.AVR_COMMAND, ((i + 1) << 4) + packets_total])
if (len(command_packet) - (i * (self.ep_size - 4))) > (self.ep_size - 4):
length = self.ep_size - 4
else:
length = len(command_packet) - (i * (self.ep_size - 4))
command_fragment.append(int(length >> 8))
command_fragment.append(int(length & 0xFF))
for j in range(0, self.ep_size - 4):
if j < length:
command_fragment.append(command_packet[i * (self.ep_size - 4) + j])
else:
command_fragment.append(0x00)
fragments.append(command_fragment)
return fragments
# Sends an AVR command and waits for response
def avr_command_response(self, command):
"""
Sends an AVR command and receives a response
:param command: Command bytes to send
:return: Response bytes received
"""
fragments = self._fragment_command_packet(command)
self.logger.debug("Sending AVR command")
for fragment in fragments:
self.logger.debug("Sending AVR command 0x{:02X}".format(fragment[0]))
resp = self.dap_command_response(fragment)
if resp[0] != self.AVR_COMMAND:
raise AvrCommandError("AVR command DAP command failed; invalid token: 0x{:02X}".format(resp[0]))
if fragment == fragments[-1]:
if resp[1] != self.AVR_FINAL_FRAGMENT:
raise AvrCommandError(
"AVR command DAP command failed; invalid final fragment ack: 0x{:02X}".format(resp[1]))
else:
if resp[1] != self.AVR_MORE_FRAGMENTS:
raise AvrCommandError(
"AVR command DAP command failed; invalid non-final fragment ack: 0x{:02X}".format(resp[1]))
# Receive response
fragment_info, _, response = self._avr_response_receive_fragment()
packets_remaining = (fragment_info & 0xF) - 1
for _ in range(0, packets_remaining):
fragment_info, _, data = self._avr_response_receive_fragment()
response.extend(data)
return response
def _avr_response_receive_fragment(self):
fragment = []
# Receive a frame
response = self._avr_response_receive_frame()
# Get the payload size from the header information
size = unpack_be16(response[2:4])
# The message header is 4 bytes, where the last two hold the size of the payload
if len(response) < (4 + size):
raise AvrCommandError("Response size does not match the header information.")
# Extract data
for i in range(0, size):
fragment.append(response[4 + i])
fragment_info = response[1]
return fragment_info, size, fragment

View File

@@ -0,0 +1,543 @@
"""
CMSIS DAP access protocol
Interfaces with CMSIS-DAP standard debuggers over HID
"""
import time
from logging import getLogger
from .dapwrapper import DapWrapper
from ..util import binary
from ..pyedbglib_errors import PyedbglibError
class CmsisDapUnit(DapWrapper):
"""Communicates with a DAP via standard CMSIS-DAP firmware stack over HID transport"""
# DAP command constants
ID_DAP_Info = 0x00
ID_DAP_HostStatus = 0x01
ID_DAP_Connect = 0x02
ID_DAP_Disconnect = 0x03
ID_DAP_TransferConfigure = 0x04
ID_DAP_Transfer = 0x05
ID_DAP_TransferBlock = 0x06
ID_DAP_TransferAbort = 0x07
ID_DAP_WriteABORT = 0x08
ID_DAP_Delay = 0x09
ID_DAP_ResetTarget = 0x0A
ID_DAP_SWJ_Pins = 0x10
ID_DAP_SWJ_Clock = 0x11
ID_DAP_SWJ_Sequence = 0x12
ID_DAP_SWD_Configure = 0x13
ID_DAP_JTAG_Sequence = 0x14
ID_DAP_JTAG_Configure = 0x15
ID_DAP_JTAG_IDCODE = 0x16
# DAP responses
DAP_OK = 0x00
DAP_ERROR = 0xff
# DAP info fields
DAP_ID_VENDOR = 0x01
DAP_ID_PRODUCT = 0x02
DAP_ID_SER_NUM = 0x03
DAP_ID_FW_VER = 0x04
DAP_ID_DEVICE_VENDOR = 0x05
DAP_ID_DEVICE_NAME = 0x06
DAP_ID_CAPABILITIES = 0xF0
DAP_ID_PACKET_COUNT = 0xFE
DAP_ID_PACKET_SIZE = 0xFF
# DAP ports
DAP_PORT_AUTODETECT = 0
DAP_PORT_DISABLED = 0
DAP_PORT_SWD = 1
DAP_PORT_JTAG = 2
def __init__(self, transport):
self.logger = getLogger(__name__)
DapWrapper.__init__(self, transport)
def _check_response(self, cmd, rsp):
"""
Checks that the response echoes the command
:param cmd: command going in
:param rsp: response coming out
"""
self.logger.debug("Checking response: cmd=0x%02X rsp=0x%02X", cmd[0], rsp[0])
if cmd[0] != rsp[0]:
raise PyedbglibError("Invalid response header")
def dap_info(self):
"""Collects the dap info"""
info = {
'vendor': self._dap_info_field(self.DAP_ID_VENDOR),
'product': self._dap_info_field(self.DAP_ID_PRODUCT),
'serial': self._dap_info_field(self.DAP_ID_SER_NUM),
'fw': self._dap_info_field(self.DAP_ID_FW_VER),
'device_vendor': self._dap_info_field(self.DAP_ID_DEVICE_VENDOR),
'device_name': self._dap_info_field(self.DAP_ID_DEVICE_NAME),
'capabilities': self._dap_info_field(self.DAP_ID_CAPABILITIES)
}
return info
def _dap_info_field(self, field):
"""
Queries one field from the dap info
:param field: which field to query
"""
self.logger.debug("dap_info (%d)", field)
cmd = bytearray(2)
cmd[0] = self.ID_DAP_Info
cmd[1] = field
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
return (rsp[2:rsp[1] + 2].decode()).strip('\0')
def dap_led(self, index, state):
"""
Operates the LED
:param index: which led
:param state: what to do with it
:return:
"""
self.logger.debug("dap_led (%d, %d)", index, state)
cmd = bytearray(3)
cmd[0] = self.ID_DAP_HostStatus
cmd[1] = index
cmd[2] = state
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
def dap_connect(self):
"""Connects to the DAP"""
self.logger.debug("dap_connect (SWD)")
cmd = bytearray(2)
cmd[0] = self.ID_DAP_Connect
cmd[1] = self.DAP_PORT_SWD
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_PORT_SWD:
raise PyedbglibError("Connect failed (0x{0:02X})".format(rsp[1]))
def dap_disconnect(self):
"""Disconnects from the DAP"""
self.logger.debug("dap_disconnect")
cmd = bytearray(1)
cmd[0] = self.ID_DAP_Disconnect
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
class CmsisDapDebugger(CmsisDapUnit):
"""ARM-specific cmsis-dap implementation"""
# SWJ pin IDs
DAP_SWJ_SWCLK_TCK = (1 << 0)
DAP_SWJ_SWDIO_TMS = (1 << 1)
DAP_SWJ_TDI = (1 << 2)
DAP_SWJ_TDO = (1 << 3)
DAP_SWJ_nTRST = (1 << 5)
DAP_SWJ_nRESET = (1 << 7)
# DAP transfer types
DAP_TRANSFER_APnDP = (1 << 0)
DAP_TRANSFER_RnW = (1 << 1)
DAP_TRANSFER_A2 = (1 << 2)
DAP_TRANSFER_A3 = (1 << 3)
DAP_TRANSFER_MATCH_VALUE = (1 << 4)
DAP_TRANSFER_MATCH_MASK = (1 << 5)
# DAP transfer responses
DAP_TRANSFER_INVALID = 0
DAP_TRANSFER_OK = (1 << 0)
DAP_TRANSFER_WAIT = (1 << 1)
DAP_TRANSFER_FAULT = (1 << 2)
DAP_TRANSFER_ERROR = (1 << 3)
DAP_TRANSFER_MISMATCH = (1 << 4)
# DP definitions
DP_IDCODE = 0x00
DP_ABORT = 0x00
DP_CTRL_STAT = 0x04
DP_WCR = 0x04
DP_SELECT = 0x08
DP_RESEND = 0x08
DP_RDBUFF = 0x0C
# JTAG-specific codes
JTAG_ABORT = 0x08
JTAG_DPACC = 0x0A
JTAG_APACC = 0x0B
JTAG_IDCODE = 0x0E
JTAG_BYPASS = 0x0F
# SWD-specific codes
SWD_AP_CSW = 0x00
SWD_AP_TAR = 0x04
SWD_AP_DRW = 0x0C
# TAR size
TAR_MAX = 0x400
# DAP CTRL_STAT bits
# Source: Coresight Techref
CSYSPWRUPACK = (1 << 31)
CSYSPWRUPREQ = (1 << 30)
CDBGPWRUPACK = (1 << 29)
CDBGPWRUPREQ = (1 << 28)
CDBGRSTACK = (1 << 27)
CDBGRSTREQ = (1 << 26)
WDATAERR = (1 << 7)
READOK = (1 << 6)
STICKYERR = (1 << 5)
STICKYCMP = (1 << 4)
TRNMODE = (1 << 2)
STICKYORUN = (1 << 1)
ORUNDETECT = (1 << 0)
# Useful CSW settings
CSW_32BIT = 0x02
CSW_16BIT = 0x01
CSW_8BIT = 0x00
CSW_ADDRINC_OFF = 0x00
CSW_ADDRINC_ON = (1 << 4)
# Supported DAP IDs.
CM0P_DAPID = 0x0BC11477
def __init__(self, transport):
self.logger = getLogger(__name__)
CmsisDapUnit.__init__(self, transport)
def dap_swj_clock(self, clock):
"""
Sets up the SWD clock timing
:param clock: clock value in Hz
"""
self.logger.debug("dap_swj_clk (%d)", clock)
cmd = bytearray(1)
cmd[0] = self.ID_DAP_SWJ_Clock
cmd.extend(binary.pack_le32(clock))
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_OK:
raise PyedbglibError("SWJ clock setting failed (0x{0:02X})".format(rsp[1]))
def dap_transfer_configure(self, idle, count, retry):
"""
Configures SWD transfers
:param idle: idle cycles
:param count: retry count
:param retry: match retry value
:return:
"""
self.logger.debug("dap_transfer_configure (%d, %d, %d)", idle, count, retry)
cmd = bytearray(2)
cmd[0] = self.ID_DAP_TransferConfigure
cmd[1] = idle
cmd.extend(binary.pack_le16(count))
cmd.extend(binary.pack_le16(retry))
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_OK:
raise PyedbglibError("Transfer configure failed (0x{0:02X})".format(rsp[1]))
def dap_swd_configure(self, cfg):
"""
Configures the SWD interface
:param cfg: turnaround and data phase config parameters
"""
self.logger.debug("dap_swd_configure (%d)", cfg)
cmd = bytearray(2)
cmd[0] = self.ID_DAP_SWD_Configure
cmd[1] = cfg
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_OK:
raise PyedbglibError("SWD configure failed (0x{0:02X})".format(rsp[1]))
def dap_reset_target(self):
"""Reset the target using the DAP"""
self.logger.debug("dap_reset_target")
cmd = bytearray(1)
cmd[0] = self.ID_DAP_ResetTarget
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_OK:
raise PyedbglibError("Reset target failed (0x{0:02X})".format(rsp[1]))
def dap_read_reg(self, reg):
"""
Reads a DAP AP/DP register
:param reg: register to read
"""
self.logger.debug("dap_read_reg (0x%02X)", reg)
cmd = bytearray(8)
cmd[0] = self.ID_DAP_Transfer
cmd[1] = 0x00 # dap
cmd[2] = 0x01 # 1 word
cmd[3] = reg | self.DAP_TRANSFER_RnW
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != 1 or rsp[2] != self.DAP_TRANSFER_OK:
raise PyedbglibError("Read reg failed (0x{0:02X}, {1:02X})".format(rsp[1], rsp[2]))
value = binary.unpack_le32(rsp[3:7])
return value
def dap_write_reg(self, reg, value):
"""
Writes a DAP AP/DP register
:param reg: register to write
:param value: value to write
"""
self.logger.debug("dap_write_reg (0x%02X) = 0x%08X", reg, value)
cmd = bytearray(4)
cmd[0] = self.ID_DAP_Transfer
cmd[1] = 0x00 # dap
cmd[2] = 0x01 # 1 word
cmd[3] = reg
cmd.extend(binary.pack_le32(value))
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != 1 or rsp[2] != self.DAP_TRANSFER_OK:
raise PyedbglibError("Write reg failed (0x{0:02X}, {1:02X})".format(rsp[1], rsp[2]))
def read_word(self, address):
"""
Reads a word from the device memory bus
:param address: address to read
"""
self.logger.debug("read word at 0x%08X", address)
self.dap_write_reg(self.SWD_AP_TAR | self.DAP_TRANSFER_APnDP, address)
return self.dap_read_reg(self.SWD_AP_DRW | self.DAP_TRANSFER_APnDP)
def write_word(self, address, data):
"""
Writes a word to the device memory bus
:param address: address to write
:param data: data to write
"""
self.logger.debug("write word at 0x%08X = 0x%08X", address, data)
self.dap_write_reg(self.SWD_AP_TAR | self.DAP_TRANSFER_APnDP, address)
self.dap_write_reg(self.SWD_AP_DRW | self.DAP_TRANSFER_APnDP, data)
@staticmethod
def multiple_of_four(x):
""" 4 byte boundary """
return x & ~0x03
def read_block(self, address, numbytes):
"""
Reads a block from the device memory bus
:param address: byte address
:param numbytes: number of bytes
"""
self.logger.debug("Block read of %d bytes at address 0x%08X", numbytes, address)
# Collect results here
result = bytearray()
# In chunks of (len-header)
max_payload_size_bytes = self.multiple_of_four(self.transport.get_report_size() - 5)
self.logger.debug("Max payload size of %d bytes", max_payload_size_bytes)
while numbytes:
# Calculate read size
read_size_bytes = max_payload_size_bytes
# Last chunk?
if read_size_bytes > numbytes:
read_size_bytes = numbytes
# Too large for TAR?
tar_max_chunk = self.TAR_MAX - (address - (address & (1-self.TAR_MAX)))
if read_size_bytes > tar_max_chunk:
read_size_bytes = tar_max_chunk
# Log
self.logger.debug("Read %d bytes from TAR address 0x%08X", read_size_bytes, address)
# Set TAR
self.dap_write_reg(self.SWD_AP_TAR | self.DAP_TRANSFER_APnDP, address)
# Read chunk
cmd = bytearray(2)
cmd[0] = self.ID_DAP_TransferBlock
cmd[1] = 0x00
cmd.extend(binary.pack_le16(read_size_bytes // 4))
cmd.extend([self.SWD_AP_DRW | self.DAP_TRANSFER_RnW | self.DAP_TRANSFER_APnDP])
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
# Check outcome
if rsp[3] != self.DAP_TRANSFER_OK:
raise PyedbglibError("Transfer failed (0x{0:02X}) address 0x{1:08X}".format(rsp[3], address))
# Extract payload
num_words_read = binary.unpack_le16(rsp[1:3])
# Check
if num_words_read * 4 != read_size_bytes:
raise PyedbglibError(
"Unexpected number of bytes returned from block read ({0:d} != {1:d})".format(num_words_read * 4,
read_size_bytes))
# Extend results
result.extend(rsp[4:4 + read_size_bytes])
numbytes -= read_size_bytes
address += read_size_bytes
return result
def write_block(self, address, data):
"""
Writes a block to the device memory bus
:param address: byte address
:param data: data
"""
self.logger.debug("Block write of %d bytes at address 0x%08X", len(data), address)
# In chunks of (len-header)
max_payload_size_bytes = self.multiple_of_four(self.transport.get_report_size() - 5)
while data:
# Calculate write size
write_size_bytes = max_payload_size_bytes
if write_size_bytes > len(data):
write_size_bytes = len(data)
# Too large for TAR?
tar_max_chunk = self.TAR_MAX - (address - (address & (1 - self.TAR_MAX)))
if write_size_bytes > tar_max_chunk:
write_size_bytes = tar_max_chunk
# Set TAR
self.dap_write_reg(self.SWD_AP_TAR | self.DAP_TRANSFER_APnDP, address)
cmd = bytearray(2)
cmd[0] = self.ID_DAP_TransferBlock
cmd[1] = 0x00
cmd.extend(binary.pack_le16(write_size_bytes // 4))
cmd.extend([self.SWD_AP_DRW | self.DAP_TRANSFER_APnDP])
cmd.extend(data[0:write_size_bytes])
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
# Shrink data buffer
data = data[write_size_bytes:]
address += write_size_bytes
def _send_flush_tms(self):
cmd = bytearray(2)
cmd[0] = self.ID_DAP_SWJ_Sequence
cmd[1] = 7 * 8
for _ in range(7):
cmd.extend([0xff])
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_OK:
raise PyedbglibError("SWJ sequence failed (0x{0:02X})".format(rsp[1]))
def init_swj(self):
"""Magic sequence to execute on pins to enable SWD in case of JTAG-default parts"""
self.logger.debug("SWJ init sequence")
# According to ARM manuals:
# Send at least 50 cycles with TMS=1
self._send_flush_tms()
# Send 16-bit switching code
cmd = bytearray(2)
cmd[0] = self.ID_DAP_SWJ_Sequence
cmd[1] = 16
cmd.extend(binary.pack_le16(0xE79E))
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_OK:
raise PyedbglibError("SWJ sequence failed (0x{0:02X})".format(rsp[1]))
# Flush TMS again
self._send_flush_tms()
# Set data low again
cmd = bytearray(3)
cmd[0] = self.ID_DAP_SWJ_Sequence
cmd[1] = 1
cmd[2] = 0x00
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
if rsp[1] != self.DAP_OK:
raise PyedbglibError("SWJ sequence failed (0x{0:02X})".format(rsp[1]))
# Now read the ID to check that it has switched
dap_id = self.dap_read_idcode()
if dap_id != self.CM0P_DAPID:
raise PyedbglibError("Invalid SWD DAP ID code! Only M0+ is currently supported.")
def dap_read_idcode(self):
"""Reads the IDCODE from the SWD DP"""
self.logger.debug("reading swd idcode")
return self.dap_read_reg(self.DP_IDCODE)
def dap_target_init(self):
"""Configures the DAP for use"""
self.logger.debug("dap_target_init")
# Clear all stickies
self.dap_write_reg(self.DP_ABORT, self.STICKYERR | self.STICKYCMP | self.STICKYORUN)
# Select to 0
self.dap_write_reg(self.DP_SELECT, 0)
# Request debug power
self.dap_write_reg(self.DP_CTRL_STAT, self.CDBGPWRUPREQ | self.CSYSPWRUPREQ)
# Most useful default of 32-bit word access with auto-increment enabled
self.dap_write_reg(self.SWD_AP_CSW | self.DAP_TRANSFER_APnDP, self.CSW_ADDRINC_ON | self.CSW_32BIT)
class CmsisDapSamDebugger(CmsisDapDebugger):
"""SAM specific CMSIS-DAP debugger"""
def dap_reset_ext(self, extend=False):
"""
Reset the target using the hardware
Some SAM devices (for example SAMDx and SAMLx) have an additional 'reset extension' capability which is not part
of the CMSIS-DAP standard. It is used to prevent the device from running after reset and then overriding its
SWD IO. The procedure is simply to hold SW_CLK low while releasing /RESET. This is done here using SWJ pins
function IF the extend argument is set.
:param extend: boolean flag to extend reset
"""
self.logger.debug("dap_reset_ext")
cmd = bytearray(7)
cmd[0] = self.ID_DAP_SWJ_Pins
cmd[1] = 0 # Reset LOW, TCK LOW
cmd[2] = self.DAP_SWJ_nRESET
if extend:
cmd[2] |= self.DAP_SWJ_SWCLK_TCK
cmd[3] = 0
cmd[4] = 0
cmd[5] = 0
cmd[6] = 0
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
cmd[1] = self.DAP_SWJ_nRESET # Reset high, TCK still low
cmd[2] = self.DAP_SWJ_nRESET
if extend:
cmd[2] |= self.DAP_SWJ_SWCLK_TCK
rsp = self.dap_command_response(cmd)
self._check_response(cmd, rsp)
# Allow Reset to be pulled high
time.sleep(0.1)

View File

@@ -0,0 +1,38 @@
"""Wrapper for any protocol over CMSIS-DAP"""
from logging import getLogger
class DapWrapper(object):
"""Base class for any CMSIS-DAP protocol wrapper"""
def __init__(self, transport):
self.logger = getLogger(__name__)
self.transport = transport
self.logger.debug("Created DapWrapper")
def dap_command_response(self, packet):
"""
Send a command, receive a response
:param packet: bytes to send
:return: response received
"""
return self.transport.hid_transfer(packet)
def dap_command_write(self, packet):
"""
Send a packet
:param packet: packed data to sent
:return: bytes sent
"""
return self.transport.hid_write(packet)
def dap_command_read(self):
"""
Receive data
:return: data received
"""
return self.transport.hid_read()

View File

@@ -0,0 +1,210 @@
"""Implements EDBG Protocol, a sub-protocol in the JTAGICE3 family of protocols."""
from logging import getLogger
from ..util.binary import unpack_be16
from .jtagice3protocol import Jtagice3Protocol
class EdbgProtocol(Jtagice3Protocol):
"""Implements EDBG protocol functionality on the JTAGICE3 protocol family"""
CMD_EDBG_QUERY = 0x00 # Capability discovery
CMD_EDBG_SET = 0x01 # Set parameters
CMD_EDBG_GET = 0x02 # Get parameters
CMD_EDBG_PROGRAM_ID_CHIP = 0x50 # Programs an ID chip
CMD_EDBG_REFRESH_ID_CHIP = 0x51 # Triggers ID chip refresh
CMD_EDBG_READ_ID_CHIP = 0x7E # Retrieve ID chip info
AVR_GET_CONFIG = 0x83 # CMSIS vendor 3 get config command
RSP_EDBG_OK = 0x80 # All OK
RSP_EDBG_LIST = 0x81 # List of items returned
RSP_EDBG_DATA = 0x84 # Data returned
RSP_EDBG_FAILED = 0xA0 # Command failed to execute
EDBG_QUERY_COMMANDS = 0x00
EDBG_CTXT_CONTROL = 0x00 # Control
EDBG_CONTROL_LED_USAGE = 0x00
EDBG_CONTROL_EXT_PROG = 0x01
EDBG_CONTROL_TARGET_POWER = 0x10
EDBG_CONFIG_KIT_DATA = 0x20 # Read the kit info flash page
"""Mapping EDBG error codes to more human friendly strings"""
EDBG_ERRORS = {0: 'SUCCESS'}
"""Mapping SHA204 response codes to more human friendly strings"""
RESPONSE_CODE = {0x00: 'SHA204_SUCCESS',
0xD2: 'SHA204_PARSE_ERROR',
0xD3: 'SHA204_CMD_FAIL',
0xD4: 'SHA204_STATUS_CRC',
0xE0: 'SHA204_FUNC_FAIL',
0xE2: 'SHA204_BAD_PARAM',
0xE4: 'SHA204_INVALID_SIZE',
0xE5: 'SHA204_BAD_CRC',
0xE6: 'SHA204_RX_FAIL',
0xE7: 'SHA204_RX_NO_RESPONSE',
0xE8: 'SHA204_RESYNC_WITH_WAKEUP',
0xF0: 'SHA204_COMM_FAIL',
0xF1: 'SHA204_TIMEOUT',
0xFA: 'ID_DATA_LOCKED',
0xFB: 'ID_CONFIG_LOCKED',
0xFC: 'ID_INVALID_SLOT',
0xFD: 'ID_DATA_PARSING_ERROR',
0xFE: 'ID_DATA_NOT_EQUAL'}
def __init__(self, transport):
self.logger = getLogger(__name__)
super(EdbgProtocol, self).__init__(
transport, Jtagice3Protocol.HANDLER_EDBG)
def check_command_exists(self, command):
"""
Check if command is supported
Runs a query to the tool to get a list of supported commands, then looks for
the input command in the list. If not supported, it raises NotImplementedError.
:param command: The command to test.
:return: None
"""
commands_supported = self.query(self.EDBG_QUERY_COMMANDS)
if command not in commands_supported:
raise NotImplementedError("Invalid command: 0x{:02X}".format(command))
def error_as_string(self, code):
"""
Get the response error as a string (error code translated to descriptive string)
:param code: error code
:return: error code as descriptive string
"""
try:
return self.EDBG_ERRORS[code]
except KeyError:
return "Unknown error!"
def response_as_string(self, code):
"""
Get the response code as a string (response code translated to descriptive string)
:param code: response code
:return: error code as descriptive string
"""
try:
return self.RESPONSE_CODE[code]
except KeyError:
return "Unknown response!"
def program_id_chip(self, id_number, data):
"""
Program the connected ID device located at the id_number with data.
:param id_number: Extension header ID number (Range 1 - 16)
:param data: A 64-byte data array to be programmed
:return: Response status from the programming
"""
self.logger.info("Programming ID chip...")
try:
self.check_command_exists(self.CMD_EDBG_PROGRAM_ID_CHIP)
except NotImplementedError as err:
self.logger.warning("Non-compliant command: %s", err)
# Old EDBG implementations contained a non-compliant version of this command
# Version 0 command
packet = bytearray([self.CMD_EDBG_PROGRAM_ID_CHIP, self.CMD_VERSION0, id_number - 1] + data)
resp = self.jtagice3_command_response_raw(packet)
self.logger.debug("Program ID response: %s", self.response_as_string(resp[3]))
return resp[3]
else:
# Version 1 command
packet = bytearray([self.CMD_EDBG_PROGRAM_ID_CHIP, self.CMD_VERSION1, id_number] + data)
status = self.check_response(self.jtagice3_command_response(packet))
self.logger.debug("Program ID response: %s", self.response_as_string(status[0]))
return status[0]
def refresh_id_chip(self):
"""
Forces a refresh of the list of connected ID devices.
:return: None
"""
self.logger.info("Refreshing ID chip...")
try:
self.check_command_exists(self.CMD_EDBG_REFRESH_ID_CHIP)
except NotImplementedError as err:
self.logger.warning("Non-compliant command: %s", err)
# Old EDBG implementations contained a non-compliant version of this command
# Version 0 command
packet = bytearray([self.CMD_EDBG_REFRESH_ID_CHIP, self.CMD_VERSION0])
resp = self.jtagice3_command_response_raw(packet)
if not resp[3] == self.RSP_EDBG_OK:
raise IOError("Invalid response from CMD_EDBG_REFRESH_ID_CHIP")
else:
# Version 1 command
packet = bytearray([self.CMD_EDBG_REFRESH_ID_CHIP, self.CMD_VERSION1])
self.check_response(self.jtagice3_command_response(packet))
def read_id_chip(self, id_number):
"""
Reads the ID information from the ID chip connected at id_number
:param id_number: Extension header ID number (Range 1 - 16)
:return: A 64-byte data array
"""
self.logger.info("Reading ID chip...")
try:
self.check_command_exists(self.CMD_EDBG_READ_ID_CHIP)
except NotImplementedError as err:
self.logger.warning("Non-compliant command: %s", err)
# Old EDBG implementations contained a non-compliant version of this command
# Version 0 command
packet = bytearray([self.CMD_EDBG_READ_ID_CHIP, self.CMD_VERSION0, id_number - 1])
resp = self.jtagice3_command_response_raw(packet)
if resp[4] == self.RSP_EDBG_DATA:
return resp[6:]
return False
else:
# Version 1 command
packet = bytearray([self.CMD_EDBG_READ_ID_CHIP, self.CMD_VERSION1, id_number])
data = self.check_response(self.jtagice3_command_response(packet))
return data
def read_edbg_extra_info(self):
"""
Reads the kit info flash page, containing board specific data
:return: A data array containing the kit info
"""
self.logger.info("Reading kit info...")
# The second parameter tells the debugger it is the only command
# The last parameter tells what to read. If zero a whole page is read, and
# if non-zero 32-bytes is fetched from offset 32 * parameter. The parameter
# cannot be greater than 8
response = self.dap_command_response(bytearray([self.AVR_GET_CONFIG, 0x01,
self.EDBG_CONFIG_KIT_DATA, 0x0]))
# Remove unused data
if len(response) >= 256 + 6:
self.logger.info("Response size is truncated")
response = response[:256 + 6]
# Byte 0 will echo the current command
# Byte 1 show the command status
if response[0] == self.AVR_GET_CONFIG:
# Check the status code
if response[1] == 0:
# Bytes [3..2] contain the received size
size = unpack_be16(response[2:4])
return response[6:size]
self.logger.warning("Command failed with error: %i", response[1])
self.logger.warning("Command was not echoed back")
return False

View File

@@ -0,0 +1,141 @@
"""Implements Housekeeping Protocol, a sub-protocol in the JTAGICE3 family of protocols."""
from logging import getLogger
from .jtagice3protocol import Jtagice3Protocol
from .jtagice3protocol import Jtagice3ResponseError
from ..util import binary
class Jtagice3HousekeepingProtocol(Jtagice3Protocol):
"""Implements housekeeping functionality on the JTAGICE3 protocol family"""
# Query contexts
HOUSEKEEPING_QUERY_COMMANDS = 0x00 # List supported commands
HOUSEKEEPING_QUERY_ANALOG_CHANNELS = 0x01 # List which analog channels are present
HOUSEKEEPING_QUERY_SPECIAL_ABILITIES = 0x02 # List special abilities
# Protocol commands
CMD_HOUSEKEEPING_START_SESSION = 0x10 # Sign on
CMD_HOUSEKEEPING_END_SESSION = 0x11 # Sign off
CMD_HOUSEKEEPING_FW_UPGRADE = 0x50 # Enter upgrade mode
# Get/Set contexts
HOUSEKEEPING_CONTEXT_CONFIG = 0x00 # Configuration parameters
HOUSEKEEPING_CONTEXT_ANALOG = 0x01 # Analog parameters
HOUSEKEEPING_CONTEXT_STATEMENT = 0x02 # Statement memory (deprecated)
HOUSEKEEPING_CONTEXT_USB = 0x03 # USB parameters
HOUSEKEEPING_CONTEXT_STATISTICS = 0x80 # Statistics
HOUSEKEEPING_CONTEXT_DIAGNOSTICS = 0x81 # Diagnostics
# Config context
HOUSEKEEPING_CONFIG_HWREV = 0x00 # Hardware version
HOUSEKEEPING_CONFIG_FWREV_MAJ = 0x01 # Major firmware version
HOUSEKEEPING_CONFIG_FWREV_MIN = 0x02 # Minor firmware version
HOUSEKEEPING_CONFIG_BUILD = 0x03 # Build number (2 bytes)
HOUSEKEEPING_CONFIG_CHIP = 0x05 # Chipset ID
HOUSEKEEPING_CONFIG_BLDR_MAJ = 0x06 # Bootloader major version
HOUSEKEEPING_CONFIG_BLDR_MIN = 0x07 # Bootloader minor version
HOUSEKEEPING_CONFIG_DEBUG_BUILD = 0x08 # Debug build flag
HOUSEKEEPING_CONFIG_FIRMWARE_IMAGE = 0x09 # Firmware Image enumerator
# USB context
HOUSEKEEPING_USB_MAX_READ = 0x00 # Maximum USB read block size
HOUSEKEEPING_USB_MAX_WRITE = 0x01 # Maximum USB write block size
HOUSEKEEPING_USB_EP_SIZE_HID = 0x10 # Current HID endpoint size
HOUSEKEEPING_USB_EP_SIZE_CDC = 0x11 # Current CDC endpoint size
# Diagnostics
HOUSEKEEPING_DIAGNOSTICS_RESET_CAUSE = 0x00 # Last reset cause
HOUSEKEEPING_DIAGNOSTICS_BOD_CTRL = 0x01 # BOD register
HOUSEKEEPING_HOST_ID = 0x02 # Debugger host device identifier
HOUSEKEEPING_HOST_REV = 0x03 # Debugger host device revision
HOUSEKEEPING_MODULE_VER_JTAG = 0x04 # Debugger host JTAG master version
HOUSEKEEPING_MODULE_VER_AW = 0x05 # Debugger host aWire master version
HOUSEKEEPING_DIAGNOSTICS_CPU_CLK = 0x06 # Debugger host CPU clock speed
# Analog
HOUSEKEEPING_ANALOG_VTREF = 0x00 # Target voltage reference value
HOUSEKEEPING_ANALOG_VTG_BUF = 0x01 # Bufferred target voltage reference
HOUSEKEEPING_ANALOG_VUSB = 0x02 # USB voltage
HOUSEKEEPING_TSUP_VOLTAGE = 0x20 # Target supply voltage setpoint
# Special Abilities
HOUSEKEEPING_ABILITY_RESET_EXTENSION = 0x00 # This tool is capable of reset extension
HOUSEKEEPING_ABILITY_HV_UPDI_ENABLE = 0x10 # This tool is capable of UPDI high-voltage activation
def __init__(self, transport):
super(Jtagice3HousekeepingProtocol, self).__init__(transport, Jtagice3Protocol.HANDLER_HOUSEKEEPING)
self.logger = getLogger(__name__)
self.logger.debug("Created AVR housekeeping protocol")
def list_supported_commands(self):
"""Uses the query interface to list all supported commands"""
self.logger.debug("Querying commands supported by this instance of housekeeping handler")
commands = self.query(self.HOUSEKEEPING_QUERY_COMMANDS)
return commands
# Direct protocol commands
def start_session(self):
"""Starts a session with the debugger (sign-on)"""
self.logger.debug("Housekeeping::start_session")
response = self.jtagice3_command_response(bytearray([self.CMD_HOUSEKEEPING_START_SESSION, self.CMD_VERSION0]))
self.check_response(response)
def end_session(self, reset_tool=False):
"""
Ends a session with the debugger (sign-off)
:param reset_tool: resets the hardware
:return:
"""
self.logger.debug("Housekeeping::end_session")
response = self.jtagice3_command_response(
bytearray([self.CMD_HOUSEKEEPING_END_SESSION, self.CMD_VERSION0, 1 if reset_tool else 0]))
self.check_response(response)
def enter_upgrade_mode(self, key=0x31727C10):
"""
Puts the debugger into firmware upgrade mode
:param key: upgrade key
:return:
"""
self.logger.debug("Housekeeping::enter_upgrade_mode")
try:
response = self.jtagice3_command_response(
bytearray([self.CMD_HOUSEKEEPING_FW_UPGRADE, self.CMD_VERSION0]) + binary.pack_be32(key))
except IOError:
self.logger.debug("IOError on enter upgrade mode. Device rebooted before response was read.")
else:
self.check_response(response)
def read_version_info(self):
"""Reads version info from the debugger"""
self.logger.debug("Housekeeping::reading version info")
# Results in dict form
versions = {
# HW version
'hardware': self.get_byte(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_HWREV),
# FW version
'firmware_major': self.get_byte(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_FWREV_MAJ),
'firmware_minor': self.get_byte(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_FWREV_MIN),
'build': self.get_le16(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_BUILD),
# BLDR
'bootloader': self.get_le16(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_BLDR_MAJ),
# Host info
'chip': self.get_byte(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_CHIP),
'host_id': self.get_le32(self.HOUSEKEEPING_CONTEXT_DIAGNOSTICS, self.HOUSEKEEPING_HOST_ID),
'host_rev': self.get_byte(self.HOUSEKEEPING_CONTEXT_DIAGNOSTICS, self.HOUSEKEEPING_HOST_REV),
# Misc
'debug': self.get_byte(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_DEBUG_BUILD)
}
# Firmware Image Requirement Enumerator is only supported on some tools
try:
versions['fire'] = self.get_byte(self.HOUSEKEEPING_CONTEXT_CONFIG, self.HOUSEKEEPING_CONFIG_FIRMWARE_IMAGE)
except Jtagice3ResponseError:
versions['fire'] = None
return versions

View File

@@ -0,0 +1,337 @@
"""JTAGICE3 protocol mappings"""
from logging import getLogger
from .avrcmsisdap import AvrCommand
from ..util import binary
from ..util import print_helpers
from ..pyedbglib_errors import PyedbglibError
class Jtagice3Command(AvrCommand):
"""
Sends a "JTAGICE3" command frame, and received a response
JTAGICE3 protocol header is formatted:
JTAGICE3_TOKEN 0x0E
PROTOCOL_VERSION 0
SEQUENCE_NUMBER_L
SEQUENCE_NUMBER_H
HANDLER_ID
PAYLOAD
Response format is:
JTAGICE3_TOKEN 0x0E
SEQUENCE_NUMBER_L echo
SEQUENCE_NUMBER_H echo
HANDLER_ID
PAYLOAD
"""
# JTAGICE3 protocol token
JTAGICE3_TOKEN = 0x0E
JTAGICE3_PROTOCOL_VERSION = 0x00
# Handlers within JTAGICE3 protocol
HANDLER_DISCOVERY = 0x00
HANDLER_HOUSEKEEPING = 0x01
HANDLER_SPI = 0x11
HANDLER_AVR8_GENERIC = 0x12
HANDLER_AVR32_GENERIC = 0x13
HANDLER_TPI = 0x14
HANDLER_EDBG = 0x20
HANDLER_COPROCESSOR = 0x21
HANDLER_POWER = 0x22
HANDLER_SELFTEST = 0x81
def __init__(self, transport, handler):
super(Jtagice3Command, self).__init__(transport)
self.logger = getLogger(__name__)
self.logger.debug("Created JTAGICE3 command")
self.handler = handler
self.sequence_id = 0
def validate_response(self, response):
"""
Validates the response form the debugger
:param response: raw response bytes
"""
self.logger.debug("Checking response (%s)", print_helpers.bytelist_to_hex_string(response))
# Check length first
if len(response) < 5:
raise PyedbglibError("Invalid response length ({:d}).".format(len(response)))
# Check token
if response[0] != self.JTAGICE3_TOKEN:
raise PyedbglibError("Invalid token (0x{:02X}) in response.".format(response[0]))
# Check sequence
sequence = response[1] + (response[2] << 8)
if self.sequence_id != sequence:
raise PyedbglibError(
"Invalid sequence in response (0x{:04X} vs 0x{:04X}).".format(self.sequence_id, sequence))
# Check handler
if response[3] != self.handler:
raise PyedbglibError("Invalid handler (0x{:02X}) in response.".format(response[3]))
def jtagice3_command_response_raw(self, command):
"""
Sends a JTAGICE3 command and receives the corresponding response
:param command:
:return:
"""
# Header
header = bytearray([self.JTAGICE3_TOKEN, self.JTAGICE3_PROTOCOL_VERSION, self.sequence_id & 0xFF,
(self.sequence_id >> 8) & 0xFF, self.handler])
# Send command, receive response
packet = header + bytearray(command)
response = self.avr_command_response(packet)
return response
def jtagice3_command_response(self, command):
"""
Sends a JTAGICE3 command and receives the corresponding response, and validates it
:param command:
:return:
"""
response = self.jtagice3_command_response_raw(command)
# Increment sequence number
self.sequence_id += 1
if self.sequence_id > 0xFFFE:
self.sequence_id = 1
# Peel and return
return response[4:]
class Jtagice3ResponseError(Exception):
"""Exception type for JTAGICE3 responses"""
def __init__(self, msg, code):
super(Jtagice3ResponseError, self).__init__(msg)
# self.message = msg
self.code = code
class Jtagice3Protocol(Jtagice3Command):
"""
Base class for all protocols in the JTAGICE3 family.
All sub-protocols support query, get and set commands.
"""
# Command versioning
CMD_VERSION0 = 0
CMD_VERSION1 = 1
# All handler share these functions:
CMD_QUERY = 0x00
CMD_SET = 0x01
CMD_GET = 0x02
# And these base responses
PROTOCOL_OK = 0x80
PROTOCOL_LIST = 0x81
PROTOCOL_DATA = 0x84
PROTOCOL_FAILED = 0xA0
# PROTOCOL_FAILED_WITH_DATA = 0xA1
# Failure codes
FAILURE_OK = 0
# CMD_SET and CMD_GET failure codes
SETGET_FAILURE_OK = 0x00
SETGET_FAILURE_NOT_IMPLEMENTED = 0x10
SETGET_FAILURE_NOT_SUPPORTED = 0x11
SETGET_FAILURE_INVALID_CLOCK_SPEED = 0x20
SETGET_FAILURE_ILLEGAL_STATE = 0x21
SETGET_FAILURE_JTAGM_INIT_ERROR = 0x22
SETGET_FAILURE_INVALID_VALUE = 0x23
SETGET_FAILURE_HANDLER_ERROR = 0x30
"""Mapping JTAGICE3 error codes to more human friendly strings"""
JTAGICE3_ERRORS = {0: 'SUCCESS'}
def __init__(self, transport, handler, supports_trailing_status=True):
super(Jtagice3Protocol, self).__init__(transport, handler)
self.logger = getLogger(__name__)
self.logger.debug("Created JTAGICE3 protocol")
self.supports_trailing_status = supports_trailing_status
def check_response(self, response, expected=None):
"""
Checks the response for known errors
:param response: response bytes
:param expected: expected response
:return: data from response
"""
status, data = self.peel_response(response, expected)
if not status:
error_message = self.error_as_string(data[0])
msg = "JTAGICE3 error response code 0x{:02X}: '{:s}' ".format(data[0], error_message)
self.logger.error(msg)
raise Jtagice3ResponseError(error_message, data[0])
return data
def error_as_string(self, code):
"""
Get the response error as a string (error code translated to descriptive string)
:param code: error code
:return: error code as descriptive string
"""
try:
return self.JTAGICE3_ERRORS[code]
except KeyError:
return "Unknown error!"
def peel_response(self, response, expected=None):
"""
Process the response, extracting error codes and data
:param response: raw response bytes
:param expected: expected response
:return: status, data
"""
return_list = False, [0xFF]
# Special handling
if expected is not None and response[0] == expected:
return_list = True, response[2:]
else:
if response[0] == self.PROTOCOL_OK:
return_list = True, []
elif response[0] == self.PROTOCOL_LIST:
return_list = True, response[2:]
elif response[0] == self.PROTOCOL_DATA:
# Trailing status is not included on some handlers
if self.supports_trailing_status and response[-1] == self.FAILURE_OK:
return_list = True, response[2:-1]
else:
return_list = False, [response[-1]]
elif response[0] == self.PROTOCOL_FAILED:
return_list = False, [response[2]]
return return_list
def query(self, context):
"""
Queries functionality using the QUERY API
:param context: Query context
:return: List of supported entries
"""
self.logger.debug("Query to context 0x{:02X}".format(context))
resp = self.jtagice3_command_response([self.CMD_QUERY, self.CMD_VERSION0, context])
status, data = self.peel_response(resp)
if not status:
msg = "Unable to QUERY (failure code 0x{:02X})".format(data[0])
raise PyedbglibError(msg)
return data
def set_byte(self, context, offset, value):
"""
Sets a single byte parameter
:param context: context (address) to set
:param offset: offset address to set
:param value: value to set
:return:
"""
self._set_protocol(context, offset, bytearray([value]))
def set_le16(self, context, offset, value):
"""
Sets a little-endian 16-bit parameter
:param context: context (address) to set
:param offset: offset address to set
:param value: value to set
"""
self._set_protocol(context, offset, binary.pack_le16(value))
def set_le32(self, context, offset, value):
"""
Sets a little-endian 32-bit parameter
:param context: context (address) to set
:param offset: offset address to set
:param value: value to set
"""
self._set_protocol(context, offset, binary.pack_le32(value))
def _set_protocol(self, context, offset, data):
"""
Generic function for setting parameters
:param context: context (address) to set
:param offset: offset address to set
:param data: values to set
"""
self.logger.debug("JTAGICE3::set {:d} byte(s) to context {:d} offset {:d}".format(len(data),
context,
offset))
resp = self.jtagice3_command_response(
bytearray([self.CMD_SET, self.CMD_VERSION0, context, offset, len(data)]) + data)
resp_status, resp_data = self.peel_response(resp)
if not resp_status:
msg = "Unable to SET (failure code 0x{:02X})".format(resp_data[0])
raise PyedbglibError(msg)
def get_byte(self, context, offset):
"""
Get a single-byte parameter
:param context: context (address) to set
:param offset: offset address to set
:return: value read
"""
data = self._get_protocol(context, offset, 1)
return data[0]
def get_le16(self, context, offset):
"""
Get a little-endian 16-bit parameter
:param context: context (address) to set
:param offset: offset address to set
:return: value read
"""
data = self._get_protocol(context, offset, 2)
return binary.unpack_le16(data)
def get_le32(self, context, offset):
"""
Get a little-endian 32-bit parameter
:param context: context (address) to set
:param offset: offset address to set
:return: value read
"""
data = self._get_protocol(context, offset, 4)
return binary.unpack_le32(data)
def _get_protocol(self, context, offset, numbytes):
"""
Generic function to get a parameter
:param context: context (address) to set
:param offset: offset address to set
:param numbytes: number of bytes to get
:return: value read
"""
self.logger.debug("JTAGICE3::get {:d} byte(s) from context {:d} offset {:d}".format(numbytes, context, offset))
resp = self.jtagice3_command_response([self.CMD_GET, self.CMD_VERSION0, context, offset, numbytes])
status, data = self.peel_response(resp)
if not status:
msg = "Unable to GET (failure code 0x{:02X})".format(data[0])
raise Jtagice3ResponseError(msg, data)
return data