mirror of
https://github.com/wagiminator/ATtiny814-USB-PD-Adapter.git
synced 2025-08-08 12:50:58 +03:00
Initial commit
This commit is contained in:
144
software/tools/pymcuprog/libs/pyedbglib/protocols/avrcmsisdap.py
Normal file
144
software/tools/pymcuprog/libs/pyedbglib/protocols/avrcmsisdap.py
Normal 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
|
543
software/tools/pymcuprog/libs/pyedbglib/protocols/cmsisdap.py
Normal file
543
software/tools/pymcuprog/libs/pyedbglib/protocols/cmsisdap.py
Normal 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)
|
@@ -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()
|
@@ -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
|
@@ -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
|
@@ -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
|
Reference in New Issue
Block a user