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,46 @@
"""
Python EDBG protocol communication library
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pyedbglib is a low-level protocol library for communicating with
Microchip CMSIS-DAP based debuggers.
pyedbglib uses HIDAPI package with a USB-level driver such as libusb.
The protocol library has no application usage on its own, but provides
USB-protocol-level tool drivers to applications such as pymcuprog.
In general a two-stage stack implementation is required for using pyedbglib:
1. Create transport HID layer
2. Create protocol implementation using this transport layer
All protocols implemented in the library generally take the transport layer
as a parameter to their constructors.
To use pyedbglib as a library for applications, the following usage patterns
can be used:
Import and instantiate transport object:
>>> from pyedbglib.hidtransport.hidtransportfactory import hid_transport
>>> transport = hid_transport()
Connect to any nEDBG tool. Serial number and product are optional, but must
be provided if more than one matching unit is connected:
>>> status = transport.connect(serial_number="", product="nedbg")
Example of application using housekeeping protocol to read out the target voltage:
>>> from pyedbglib.protocols.housekeepingprotocol import Jtagice3HousekeepingProtocol
>>> housekeeper = Jtagice3HousekeepingProtocol(transport)
>>> housekeeper.start_session()
>>> voltage = housekeeper.get_le16(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG,
Jtagice3HousekeepingProtocol.HOUSEKEEPING_ANALOG_VTREF)
>>> voltage = voltage / 1000.0
>>> housekeeper.end_session()
>>> print ("Target is running at {0:.02f}V".format(voltage))
"""
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

View File

@@ -0,0 +1,165 @@
"""Base class for all HID transport mechanisms."""
from logging import getLogger
from . import toolinfo
class HidTool(object):
"""
Holds transport and DAP properties of a CMSIS-DAP debugger.
Used to select the debugger to use if multiple debuggers are connected.
"""
# pylint: disable=too-many-instance-attributes, too-many-arguments
# These are primary keys used to identify the debugger.
def __init__(self, vendor_id, product_id, serial_number, product_string="", manufacturer_string=""):
self.logger = getLogger(__name__)
self.interface_number = -1
self.vendor_id = vendor_id
self.product_id = product_id
self.serial_number = serial_number
self.product_string = product_string
self.manufacturer_string = manufacturer_string
self.firmware_version = ""
self.device_vendor_id = ""
self.device_name = ""
self.packet_size = 64
def set_packet_size(self, packet_size):
"""
Sets the packet size
:param packet_size: bytes per packet
"""
self.packet_size = packet_size
def set_product_string(self, product_string):
"""
Sets the product string
:param product_string: product name string
"""
self.product_string = product_string
class HidTransportBase(object):
"""Base class for HID transports"""
def __init__(self):
self.logger = getLogger(__name__)
self.devices = []
self.device = None
self.detect_devices()
self.connected = False
def __del__(self):
# Make sure we always disconnect the HID connection
self.disconnect()
def detect_devices(self):
"""Raise error as this method needs to be overridden."""
raise NotImplementedError("method needs to be defined by sub-class")
def get_matching_tools(self, serial_number_substring='', product=None):
"""
Returns a list of tools matching the given serial_number_substring and product.
:param serial_number_substring: can be an empty string or a subset of a serial number. Not case sensitive
This function will do matching of the last part of the devices serial numbers to
the serial_number_substring. Examples:
'123' will match "MCHP3252000000043123" but not "MCP32520001230000000"
'' will match any serial number
:param product: product type to connect to. If None any tool matching the serial_number_substring
will be returned
:return: List of matching tools
"""
# Support systems which use None as the standard for a unspecified USB serial
if serial_number_substring is None:
serial_number_substring = ""
# Making serial_number_substring case insensitive
serial_number_substring = serial_number_substring.lower()
# Support tool shortnames
toolname_in_product_string = toolinfo.tool_shortname_to_product_string_name(product)
if toolname_in_product_string is not None:
# Making product name case insensitive
toolname_in_product_string = toolname_in_product_string.lower()
matching_devices = []
for device in self.devices:
if toolname_in_product_string is None or device.product_string.lower().startswith(
toolname_in_product_string):
if device.serial_number.lower().endswith(serial_number_substring):
matching_devices.append(device)
return matching_devices
def connect(self, serial_number=None, product=None):
"""
Makes a HID connection to a debugger
:param serial_number: instance serial number to connect to
:param product: product type to connect to
:return: True if successfully connected to a tool, False if not
"""
if self.connected:
return True
device_count = len(self.devices)
self.logger.debug("{:d} devices available".format(device_count))
if device_count == 0:
self.logger.error("No CMSIS-DAP devices found.")
return False
matching_devices = self.get_matching_tools(serial_number_substring=serial_number, product=product)
number_of_matching_devices = len(matching_devices)
# Did we find exactly 1 tool?
if number_of_matching_devices != 1:
log_str = "Found {:d} daps matching the filter serial = \"{}\" and product = \"{}\""
self.logger.debug(log_str.format(number_of_matching_devices, serial_number, product))
if number_of_matching_devices > 1:
self.logger.error("Too many products found. Please specify one of:")
for device in self.devices:
self.logger.error(" > {:s} {:s}".format(device.product_string,
device.serial_number))
return False
# Everything is peachy, connect to the tool
self.device = matching_devices[0]
self.hid_connect(self.device)
self.logger.debug("Connected OK")
self.connected = True
packet_size = toolinfo.get_default_report_size(self.device.product_id)
self.device.set_packet_size(packet_size)
self.hid_info()
return True
def disconnect(self):
"""Release the HID connection"""
if self.connected:
self.hid_disconnect()
self.connected = False
def hid_connect(self, device):
"""Raise error as this method needs to be overridden."""
raise NotImplementedError("method needs to be defined by sub-class")
def hid_info(self):
"""Raise error as this method needs to be overridden."""
raise NotImplementedError("method needs to be defined by sub-class")
def hid_disconnect(self):
"""Raise error as this method needs to be overridden."""
raise NotImplementedError("method needs to be defined by sub-class")
def get_report_size(self):
"""
Get the packet size in bytes
:return: bytes per packet/report
"""
return self.device.packet_size

View File

@@ -0,0 +1,56 @@
"""
Factory for HID transport connections.
Currently supports only Cython/HIDAPI
"""
import platform
from logging import getLogger
from ..pyedbglib_errors import PyedbglibNotSupportedError
def hid_transport(library="hidapi"):
"""
Dispatch a transport layer for the OS in question
The transport layer is typically used to connect to a tool and then it is passed in as a parameter when creating
protocol objects. An example where the transport layer is used to create an instance of the housekeepingprotocol
for communication with the nEDBG debugger::
from pyedbglib.hidtransport.hidtransportfactory import hid_transport
transport = hid_transport()
connect_status = False
try:
connect_status = transport.connect(serial_number='', product='nedbg')
except IOError as error:
print("Unable to connect to USB device ({})".format(error))
if not connect_status:
print("Unable to connect to USB device")
housekeeper = housekeepingprotocol.Jtagice3HousekeepingProtocol(transport)
:param library: Transport library to use, currently only 'hidapi' is supported which will use the libusb hidapi
:type library: string
:returns: Instance of transport layer object
:rtype: class:cyhidapi:CyHidApiTransport
"""
logger = getLogger(__name__)
operating_system = platform.system().lower()
logger.debug("HID transport using library '{:s}' on OS '{:s}'".format(library, operating_system))
# HID API is the primary transport
if library == 'hidapi':
hid_api_supported_os = ['windows', 'darwin', 'linux', 'linux2']
if operating_system in hid_api_supported_os:
from .cyhidapi import CyHidApiTransport
return CyHidApiTransport()
msg = "System '{0:s}' not implemented for library '{1:s}'".format(operating_system, library)
logger.error(msg)
raise PyedbglibNotSupportedError(msg)
# Other transports may include cmsis-dap DLL, atusbhid (dll or so) etc
msg = "Transport library '{0}' not implemented.".format(library)
logger.error(msg)
raise PyedbglibNotSupportedError(msg)

View File

@@ -0,0 +1,94 @@
"""Gathering of all known Microchip CMSIS-DAP debuggers and default EP sizes"""
from logging import getLogger
# List of known useful HID/CMSIS-DAP tools
# 3G tools:
USB_TOOL_DEVICE_PRODUCT_ID_JTAGICE3 = 0x2140
USB_TOOL_DEVICE_PRODUCT_ID_ATMELICE = 0x2141
USB_TOOL_DEVICE_PRODUCT_ID_POWERDEBUGGER = 0x2144
USB_TOOL_DEVICE_PRODUCT_ID_EDBG_A = 0x2111
USB_TOOL_DEVICE_PRODUCT_ID_ZERO = 0x2157
USB_TOOL_DEVICE_PRODUCT_ID_MASS_STORAGE = 0x2169
USB_TOOL_DEVICE_PRODUCT_ID_PUBLIC_EDBG_C = 0x216A
USB_TOOL_DEVICE_PRODUCT_ID_KRAKEN = 0x2170
# 4G tools:
USB_TOOL_DEVICE_PRODUCT_ID_MEDBG = 0x2145
# 5G tools:
USB_TOOL_DEVICE_PRODUCT_ID_NEDBG_HID_MSD_DGI_CDC = 0x2175
USB_TOOL_DEVICE_PRODUCT_ID_PICKIT4_HID_CDC = 0x2177
USB_TOOL_DEVICE_PRODUCT_ID_SNAP_HID_CDC = 0x2180
# The Product String Names are used to identify the tool based on the USB
# device product strings (i.e. these names are usually just a subset of the
# actual product strings)
TOOL_SHORTNAME_TO_USB_PRODUCT_STRING = {
'atmelice': "Atmel-ICE",
'powerdebugger': "Power Debugger",
'pickit4': "MPLAB PICkit 4",
'snap': "MPLAB Snap",
'nedbg': "nEDBG",
'jtagice3': "JTAGICE3",
'medbg': "mEDBG",
'edbg': "EDBG",
}
def get_default_report_size(pid):
"""
Retrieve default EP report size based on known PIDs
:param pid: product ID
:return: packet size
"""
logger = getLogger(__name__)
hid_tools = [
# 3G
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_JTAGICE3, 'default_report_size': 512},
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_ATMELICE, 'default_report_size': 512},
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_POWERDEBUGGER, 'default_report_size': 512},
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_EDBG_A, 'default_report_size': 512},
# 4G
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_MEDBG, 'default_report_size': 64},
# 5G
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_NEDBG_HID_MSD_DGI_CDC, 'default_report_size': 64},
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_PICKIT4_HID_CDC, 'default_report_size': 64},
{'pid': USB_TOOL_DEVICE_PRODUCT_ID_SNAP_HID_CDC, 'default_report_size': 64}]
logger.debug("Looking up report size for pid 0x{:04X}".format(pid))
for tool in hid_tools:
if tool['pid'] == pid:
logger.debug("Default report size is {:d}".format(tool['default_report_size']))
return tool['default_report_size']
logger.debug("PID not found! Reverting to 64b.")
return 64
def tool_shortname_to_product_string_name(shortname):
"""
Mapping for common short names of tools to product string name
The intention is that this function is always run on the tool name and that the conversion
only happens if the name is a known shortname. If the shortname is not known of if the name
provided is already a valid Product string name then the provided shortname parameter will
just be returned unchanged. So if the name already is a correct Product string name it is
still safe to run this conversion funtion on it.
:param shortname: shortname typically used by atbackend (powerdebugger, atmelice etc.)
:return: String to look for in USB product strings to identify the tool
"""
logger = getLogger(__name__)
if shortname is None:
logger.debug("Tool shortname is None")
# This is also valid as the user might have provided no tool name, but the conversion function
# should still be valid
return shortname
shortname_lower = shortname.lower()
if shortname_lower not in TOOL_SHORTNAME_TO_USB_PRODUCT_STRING:
logger.debug("%s is not a known tool shortname", shortname)
# ...but it could be a valid Product string name already so no reason to report an error
return shortname
return TOOL_SHORTNAME_TO_USB_PRODUCT_STRING[shortname_lower]

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

View File

@@ -0,0 +1,21 @@
"""
pyedbglib specific exceptions
"""
class PyedbglibError(Exception):
"""
Base class for all pyedbglib specific exceptions
"""
def __init__(self, msg=None, code=0):
super(PyedbglibError, self).__init__(msg)
self.code = code
class PyedbglibNotSupportedError(PyedbglibError):
"""
Signals that an attempted operation is not supported
"""
def __init__(self, msg=None, code=0):
super(PyedbglibNotSupportedError, self).__init__(msg)
self.code = code

View File

@@ -0,0 +1,146 @@
"""Packing and unpacking numbers into bytearrays of 8-bit values with various endian encodings"""
from numbers import Integral
def _check_input_value(value, bits):
"""
:param value: An integer
:param bits: Number of bits used to represent this integer
:return: Raises an OverflowError if the value is too large
"""
# Be sure to support both py2 and py3
if not isinstance(value, Integral):
raise TypeError("The input {} is not an Integral type".format(value))
if value > (2 ** bits) - 1:
raise OverflowError("Value {} is larger than the maximum value {}".format(value, (2 ** bits) - 1))
def pack_le32(value):
"""
:param value: input value
:return: 32-bit little endian bytearray representation of the input value
"""
_check_input_value(value, 32)
return bytearray([value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF])
def pack_be32(value):
"""
:param value: input value
:return: 32-bit big endian bytearray representation of the input value
"""
_check_input_value(value, 32)
return bytearray(
[(value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF])
def pack_le24(value):
"""
:param value: input value
:return: 24-bit little endian bytearray representation of the input value
"""
_check_input_value(value, 24)
return bytearray([value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF])
def pack_be24(value):
"""
:param value: input value
:return: 24-bit big endian bytearray representation of the input value
"""
_check_input_value(value, 24)
return bytearray(
[(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF])
def pack_le16(value):
"""
:param value: input value
:return: 16-bit little endian bytearray representation of the input value
"""
_check_input_value(value, 16)
return bytearray([value & 0xFF, (value >> 8) & 0xFF])
def pack_be16(value):
"""
:param value: input value
:return: 16-bit big endian bytearray representation of the input value
"""
_check_input_value(value, 16)
return bytearray([(value >> 8) & 0xFF, value & 0xFF])
def _check_input_array(data, length):
"""
Used to check if a bytearray or list of 8-bit values has the correct length to convert to an integer
:param data: bytearray (or list) representing a value
:param length: Expected length of the list
:return: Raises a ValueError if len(data) is not the same as length
"""
if not isinstance(data, (list, bytearray)):
raise TypeError("The input {} is not a list of bytearray".format(data))
if len(data) != length:
raise ValueError("Input data {} does not have length {}".format(data, length))
def unpack_le32(data):
"""
:param data: 32-bit little endian bytearray representation of an integer
:return: integer value
"""
_check_input_array(data, 4)
return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24)
def unpack_be32(data):
"""
:param data: 32-bit big endian bytearray representation of an integer
:return: integer value
"""
_check_input_array(data, 4)
return data[3] + (data[2] << 8) + (data[1] << 16) + (data[0] << 24)
def unpack_le24(data):
"""
:param data: 24-bit little endian bytearray representation of an integer
:return: integer value
"""
_check_input_array(data, 3)
return data[0] + (data[1] << 8) + (data[2] << 16)
def unpack_be24(data):
"""
:param data: 24-bit big endian bytearray representation of an integer
:return: integer value
"""
_check_input_array(data, 3)
return data[2] + (data[1] << 8) + (data[0] << 16)
def unpack_le16(data):
"""
:param data: 16-bit little endian bytearray representation of an integer
:return: integer value
"""
_check_input_array(data, 2)
return data[0] + (data[1] << 8)
def unpack_be16(data):
"""
:param data: 16-bit big endian bytearray representation of an integer
:return: integer value
"""
_check_input_array(data, 2)
return data[1] + (data[0] << 8)

View File

@@ -0,0 +1,8 @@
"""Generating string representations of variables for nice printouts"""
def bytelist_to_hex_string(bytelist):
"""
:param bytelist: list of byte values
:return: String representation of the bytelist with each item as a byte value on the format 0xXX
"""
return '[' + ', '.join("0x%02X" % x for x in bytelist) + ']'