mirror of
https://github.com/wagiminator/ATtiny814-USB-PD-Adapter.git
synced 2025-08-07 12:50:28 +03:00
Initial commit
This commit is contained in:
46
software/tools/pymcuprog/libs/pyedbglib/__init__.py
Normal file
46
software/tools/pymcuprog/libs/pyedbglib/__init__.py
Normal 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())
|
@@ -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
|
@@ -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)
|
@@ -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]
|
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
|
21
software/tools/pymcuprog/libs/pyedbglib/pyedbglib_errors.py
Normal file
21
software/tools/pymcuprog/libs/pyedbglib/pyedbglib_errors.py
Normal 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
|
146
software/tools/pymcuprog/libs/pyedbglib/util/binary.py
Normal file
146
software/tools/pymcuprog/libs/pyedbglib/util/binary.py
Normal 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)
|
@@ -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) + ']'
|
Reference in New Issue
Block a user