Last active
April 18, 2020 01:28
-
-
Save darkarnium/a87ada968b32455247593d0cac261038 to your computer and use it in GitHub Desktop.
Provides a very basic FT2232H SWD implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' 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