Skip to content

Instantly share code, notes, and snippets.

@darkarnium
Last active April 18, 2020 01:28
Show Gist options
  • Save darkarnium/a87ada968b32455247593d0cac261038 to your computer and use it in GitHub Desktop.
Save darkarnium/a87ada968b32455247593d0cac261038 to your computer and use it in GitHub Desktop.
Provides a very basic FT2232H SWD implementation
''' Provides a very basic (read: shitty) FT2232H SWD implementation. '''
import time
import logging
import binascii
from struct import pack
from struct import unpack
from operator import xor
from pyftdi.gpio import GpioController
class FT2232SWD:
SWD_ACK_OK = 0x1
SWD_ACK_WAIT = 0x2
SWD_ACK_FAULT = 0x4
# Define SWD sequences (MSb first, because I'm lazy and it works).
SWD_JTAG_TO_SWD = [0x79, 0xE7]
SWD_READ_IDCODE = [0xA5]
def __init__(self, swclk=0x01, swdio=0x02):
''' Initialize the FT2232 and perform SWD initialization. '''
self.log = logging.getLogger(__name__)
# Set the target clock cycle interval. This isn't going to be accurate
# at all - especially under load - so, good luck, have fun!
self.cycle = 0.0001
self.sleep = self.cycle / 2
# Defaults are:
# Pin D0 - 0x01 - OUT (TCK)
# Pin D1 - 0x02 - OUT (TDI / TDO)
self.swclk = swclk
self.swdio = swdio
# Setup GPIO.
self.log.debug("Setting up FT2232 for GPIO")
self.gpio = GpioController()
self.gpio.open_from_url(url='ftdi://0x0403:0x6010/1', direction=0xFF)
# Pull everything low to begin with.
self.state = 0x0
self.log.debug("Setting the initial GPIO state to %s", self.state)
self.gpio.write_port(self.state)
# Perform a line-reset.
# "A line reset is performed by clocking at least 50 cycles with
# the SWDIO line kept HIGH by the Host".
self.log.info("Sending SWD line-reset")
self._write_bits([0x1] * 50)
# Switch the debug port to SWD.
self.log.info("Sending SWD JTAG_TO_SWD sequence")
self._write_bytes(self.SWD_JTAG_TO_SWD)
# ...and perform a final line-reset.
self.log.info("Sending SWD line-reset")
self._write_bits([0x1] * 50)
self._write_bits([0x0] * 8)
def _write_bits(self, bits):
''' Write bits onto the wire (Master to Target) communication. '''
self.log.debug("Writing bits: %s", bits)
for bit in bits:
# Pull the clock HIGH.
self.state |= self.swclk
self.gpio.write_port(self.state)
time.sleep(self.sleep)
# Check whether we need to write a HIGH or LOW for the bit to be
# transmitted (where HIGH is 1).
time.sleep(self.sleep)
if bit == 1:
self.state |= self.swdio
else:
self.state &= ~self.swdio
# Send data via SWDIO on the falling-edge of the clock.
self.state &= ~self.swclk
self.gpio.write_port(self.state)
def _write_bytes(self, data):
''' Converts bytes to bits, and writes.. '''
bits = self._bytes_to_bits(data)
self._write_bits(bits)
def _read_bits(self, count):
''' Reads N bits from the wire (Target to Master) communication. '''
self.log.debug("Reading %s bits", count)
# First, ensure that the SWDIO pin is set to IN, rather than OUT, and
# leave it the fuck alone.
self.gpio.set_direction(self.swdio, 0x0)
response = []
for _ in range(count):
# Data will be banged onto the wire by the target device on the
# rising edge.
self.state |= self.swclk
self.gpio.write_port(self.state)
# Finally, read the state of SWDIO to determine the value sent by
# the target.
if(self.gpio.read() & self.swdio) == self.swdio:
response.append(1)
else:
response.append(0)
# Sleep and then drive the clock LOW to complete the cycle.
self.state &= ~self.swclk
time.sleep(self.sleep)
self.gpio.write_port(self.state)
self.log.debug("Read %s", response)
return response
def _write_trn(self):
''' Handles writing the 'Turn Around' sequence. '''
self._write_bits([0x0])
def read_idcode(self):
'''
Provides a wrapper to send an SWD IDCODE request and read the
response.
'''
self.log.info('Sending SWD IDCODE request')
self._write_bytes(self.SWD_READ_IDCODE)
self._write_trn()
# Check the result of the READID operation.
ack = self._bits_to_bytes(self._read_bits(3))
if ack == self.SWD_ACK_WAIT:
# TODO: Not really exception worthy, we can wait.
raise Exception("Received SWD_ACK_WAIT")
elif ack == self.SWD_ACK_FAULT:
raise Exception("Received SWD_ACK_FAULT")
# Okay, we're good! (Maybe)
self.log.info("Recieved SWD_ACK_OK, reading data...")
idcode = self._bits_to_bytes(self._read_bits(32))
# TODO: Yeah, we should be using this...
parity = self._read_bits(1)
self.log.info("Read IDCODE: %s", hex(idcode))
return idcode
def _bytes_to_bits(self, data):
''' Convert bytes to a list of bits. '''
result = []
# This is likely the shittiest way to do this! Woo!
for byte in data:
for bit in list('{0:08b}'.format(byte)):
result.append(1 if bit == '1' else 0)
return result
def _bits_to_bytes(self, data):
''' Convert a list of bits to bytes - also flips LSb to MSb.'''
result = 0x0
for idx, bit in enumerate(data):
result |= bit << idx
return result
if __name__ == '__main__':
# Setup a logger for debugging.
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(process)d - [%(levelname)s] %(message)s',
)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# Kick it!
swd = FT2232SWD()
swd.read_idcode()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment