Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
shikra-programmer/shikra.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
566 lines (490 sloc)
19.3 KB
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
#! /usr/bin/env python | |
import cmd | |
import usb.core | |
import struct | |
import sys | |
# shikra USB vendor/product | |
ID_VENDOR = 0x403 | |
ID_PRODUCT = 0x6014 | |
USB_RELEASE_VERSION = 0x0900 | |
EEPROM_SIZE = 256 | |
BYTES_AT_A_TIME = 2 # we need to read 2 "bytes" at a time since each word in eeprom is 16bit, so 2 8bit bytes in python. | |
# this is the minimum amount of data we can do on a CTRL transfer. | |
READ_REQ_TYPE = 0xc0 | |
READ_REQ = 0x90 | |
READ_VAL = 0 | |
WRITE_REQ_TYPE = 0x40 | |
WRITE_REQ = 0x91 | |
DUMP_WIDTH = 16 # how many python bytes to print per row in a hex dump. this is 8, 16bit words per line. | |
# shikra LED eeprom values | |
LED_TX = 0x10 | |
LED_TRISTATE = 0x00 | |
LED_RX = 0x20 | |
LED_TXRX = 0x30 | |
LED_DRIVE_0 = 0x60 | |
# eeprom type 93C56 | |
EEPROM_TYPE = 0x56 | |
# shikra modes | |
RS232_MODE = 0x10 | |
# notes | |
# http://www.graphics.cornell.edu/~westin/canon/ch03.html | |
# bmRequestType is 0xC0 during read and 0x40 during write. | |
# https://developer.cisco.com/site/eiot/documents/pyusb-dev-guide/#pyusb-hello-world-program | |
# http://www.element14.com/community/community/designcenter/single-board-computers/next-gen_beaglebone/blog/2013/07/15/bbb--usb-io-with-ftdi-ft2232h | |
# https://github.com/walac/pyusb/blob/master/usb/control.py | |
# https://learn.adafruit.com/adafruit-ft232h-breakout/windows-setup | |
# http://sourceforge.net/p/ftdi-usb-sio/mailman/message/4081015/ | |
# https://github.com/richardeoin/ftx-prog/blob/master/ftx_prog.c | |
# Shikra LED on board is D1. | |
# pin 33 on FT232H / ACBUS9 (https://cdn.shopify.com/s/files/1/0244/5107/products/Screen_Shot_2014-07-28_at_3.52.56_PM_1024x1024.png?v=1423861544) | |
class ShikraCLI(cmd.Cmd): | |
''' | |
shikra command line interface | |
''' | |
def preloop(self): | |
self.prompt = "shikra> " | |
self.intro = self.welcome() | |
pass | |
def postloop(self): | |
pass | |
def do_exit(self, args): | |
print("Exiting SHIKRA programming utility...") | |
return True | |
def do_EOF(self, args): | |
print "" | |
return True | |
def do_find_shikra(self, args): | |
''' | |
Look for Shikra USB device and do initial configuration. | |
This should be run before other configuration steps. | |
''' | |
print "[+] Looking for Shikra..." | |
found = SHIKRA.find() | |
if(found): | |
print "[+] Shikra device found." | |
SHIKRA.config() | |
# go to subloop if device is found. | |
foundcli = ShikraFoundCLI() | |
foundcli.cmdloop() | |
else: | |
print "[+] Shikra device not found. Try unplugging and plugging the Shikra back in." | |
def welcome(self): | |
line = "[+] Welcome to the SHIKRA programming utility by XIPITER.\n" | |
shikra_logo = ''' | |
###### ### ## ### ### ## ###### #### | |
### ## ### ## ### ### ## ### ## ##### | |
#### ### ## ### ##### ### ## ## ### | |
##### ####### ### ##### ###### ## ### | |
#### ### ## ### ### ## ### ## ######## | |
## ### ### ## ### ### ## ### ## ## ### | |
##### ### ## ### ### ## ### ## ## ### | |
''' | |
return line + shikra_logo | |
class ShikraFoundCLI(cmd.Cmd): | |
def preloop(self): | |
self.prompt = "shikra programming> " | |
pass | |
def do_exit(self, args): | |
print("Exiting SHIKRA programming utility...") | |
return True | |
def do_EOF(self, args): | |
print "" | |
return True | |
def do_set_led_tx(self, args): | |
''' | |
Turn the Shikra LED on during data transmission. | |
''' | |
self.warn_led() | |
SHIKRA.setLEDTx() | |
def do_set_led_rx(self, args): | |
''' | |
Turn the Shikra LED on during data recieve. | |
''' | |
self.warn_led() | |
SHIKRA.setLEDRx() | |
def do_set_led_txrx(self, args): | |
''' | |
Turn the Shikra LED on during data recieved and transmit. | |
''' | |
self.warn_led() | |
SHIKRA.setLEDTxRx() | |
def do_set_led_on(self, args): | |
''' | |
Turn the Shikra LED on solid. | |
''' | |
self.warn_led() | |
SHIKRA.setLEDOn() | |
def do_set_led_off(self, args): | |
''' | |
Turn Shikra LED off. | |
''' | |
self.warn_led() | |
SHIKRA.setLEDOff() | |
def do_write_config(self, args): | |
''' | |
Write current configuration to Shikra. | |
''' | |
template = SHIKRA.createEEPROMWriteTemplate() | |
SHIKRA.writeEEPROM(template) | |
def do_dump(self, args): | |
''' | |
Dumps the device EEPROM values stored on the Shikra to the screen | |
in Hex dump format. | |
''' | |
dump = SHIKRA.dumpEEPROM() | |
print SHIKRA.printEEPROM(dump) | |
def do_print_config(self, args): | |
''' | |
Prints the current programming utility configuration to the screen in hex dump format. | |
This does not print what is on the shikra EEPROM, `dump` command does that. This prints | |
the EEPROM values that will be written on `write_config` command. | |
''' | |
template = SHIKRA.createEEPROMWriteTemplate() | |
print SHIKRA.printEEPROM(template) | |
def do_factory_reset(self, args): | |
''' | |
Restore shikra eeprom to factory defaults of 0xffff in each word | |
''' | |
SHIKRA.factoryResetEEPROM() | |
def do_zero(self, args): | |
''' | |
Write over shikra eeprom with 0x0000 for every word. | |
''' | |
SHIKRA.zeroEEPROM() | |
def do_backup(self, filename): | |
''' | |
Backup shikra device eeprom configuration to file specified by filename. | |
Format is Hex dump format that resembles FTDI's FT_PROG format. | |
''' | |
if(filename): | |
try: | |
f = open(filename, "w+") | |
template = SHIKRA.dumpEEPROM() | |
eeprom_string = SHIKRA.printEEPROM(template) | |
f.write(eeprom_string) | |
f.close() | |
print "[+] Backup successful to file: {0}".format(filename) | |
except: | |
print "[+] Trouble writing to file {0}".format(filename) | |
else: | |
print "[+] Filename needed." | |
def do_restore_from_backup(self, filename): | |
''' | |
After doing `backup` you can restore the eeprom from a backup file into the | |
Shikra device's onboard EEPROM memory. | |
''' | |
SHIKRA.restoreEEPROMFromFile(filename) | |
def warn_led(self): | |
''' | |
Warn users that LED functions for older shikra models may not work. | |
''' | |
print "[+] WARNING: LED programming methods may not work for older Shikra Models." | |
class Shikra(): | |
''' | |
deals with setting up device interface on USB bus. | |
''' | |
def __init__(self): | |
self.shikra_dev = "" | |
self.read_ep = "" | |
self.write_ep = "" | |
self.led_config = None | |
self.mode = RS232_MODE | |
def find(self): | |
''' | |
find shikra device | |
''' | |
self.shikra_dev = usb.core.find(idVendor=ID_VENDOR, idProduct=ID_PRODUCT) | |
if self.shikra_dev is not None: | |
return True | |
else: | |
return False | |
def config(self): | |
# set first active | |
self.shikra_dev.set_configuration() | |
# get endpoint | |
cfg = self.shikra_dev.get_active_configuration() | |
intf = cfg[0, 0] | |
self.read_ep = usb.util.find_descriptor(intf, custom_match=lambda e: e.bEndpointAddress == 0x81) | |
self.write_ep = usb.util.find_descriptor(intf, custom_match=lambda e: e.bEndpointAddress == 0x2) | |
# -------------------- Shikra EEPROM programming methods ---------------------------# | |
def readWordFromEEPROM(self, index): | |
''' | |
read byte from EEPROM on Shikra. | |
''' | |
out = self.shikra_dev.ctrl_transfer(READ_REQ_TYPE, READ_REQ, 0x00, index, data_or_wLength=BYTES_AT_A_TIME) | |
s = struct.unpack('B' * BYTES_AT_A_TIME, out) | |
# word from eeprom is 2 bytes, represented as two integers in struct returned. | |
return s | |
def writeWordToEEPROM(self, index, value): | |
''' | |
Write a 'Word' to EEPROM. A Word on the shikra is 16bits, or two bytes on x86. | |
value is like 0xFFFF or 0x0000, two bytes | |
''' | |
self.shikra_dev.ctrl_transfer(WRITE_REQ_TYPE, WRITE_REQ, value, index, data_or_wLength=BYTES_AT_A_TIME) | |
def dumpEEPROM(self): | |
''' | |
reads all bytes from the shikra device eeprom and returns a packed structure | |
for printing, writing, restoring, etc. | |
''' | |
addrcount = 0 | |
string_struct = "" | |
for index in xrange(EEPROM_SIZE / BYTES_AT_A_TIME): | |
byte = self.readWordFromEEPROM(addrcount) | |
string_struct += struct.pack(">B", byte[0]) | |
string_struct += struct.pack(">B", byte[1]) | |
addrcount += 1 | |
eeprom_list = [] | |
for byte in string_struct: | |
eeprom_list.append(byte) | |
return eeprom_list | |
def printEEPROM(self, eeprom_list): | |
''' | |
takes a packed structure as input as eeprom_list. | |
Prints a "hex dump" style format of the eeprom_list. | |
''' | |
eeprom_string = "" | |
width = 0 | |
count = 0 | |
for index in xrange(EEPROM_SIZE / BYTES_AT_A_TIME): | |
byte0 = "{0:0{1}x}".format(ord(eeprom_list[count]), 2).upper() | |
byte1 = "{0:0{1}x}".format(ord(eeprom_list[count + 1]), 2).upper() | |
if(width < DUMP_WIDTH): | |
# print in reverse order due to endian-ness | |
eeprom_string += byte1 | |
eeprom_string += byte0 | |
eeprom_string += " " | |
else: | |
eeprom_string += "\n" | |
eeprom_string += byte1 | |
eeprom_string += byte0 | |
eeprom_string += " " | |
width = 0 | |
count += 2 | |
width += 2 | |
return eeprom_string | |
def zeroEEPROM(self): | |
''' | |
write zeros to all 128bytes of shikra EEPROM. | |
''' | |
for index in xrange(EEPROM_SIZE / BYTES_AT_A_TIME): | |
# we can read and write 2 | |
self.writeWordToEEPROM(index, 0x0000) | |
def factoryResetEEPROM(self): | |
''' | |
return to factory defaults of 0xffff for every word | |
''' | |
for index in xrange(EEPROM_SIZE / BYTES_AT_A_TIME): | |
self.writeWordToEEPROM(index, 0xFFFF) | |
def setLEDOff(self): | |
''' | |
turn shikra led mode off. Sets ACBUS9 to high voltage to inhibit current through LED. | |
''' | |
self.led_config = LED_TRISTATE | |
def setLEDOn(self): | |
''' | |
test with programming eeprom the pulls ACBUS9 down to 0.00v to turn LED on continously. | |
''' | |
self.led_config = LED_DRIVE_0 | |
def setLEDTx(self): | |
''' | |
configure the led to blink on transmit data. | |
''' | |
self.led_config = LED_TX | |
def setLEDRx(self): | |
''' | |
configure the led to blink on recieve data. | |
''' | |
self.led_config = LED_RX | |
def setLEDTxRx(self): | |
''' | |
configure the led to blink on recieve data. | |
''' | |
self.led_config = LED_TXRX | |
def writeEEPROM(self, eeprom_template): | |
''' | |
write out packed data structure to eeprom on device. | |
''' | |
count = 0 | |
for index in xrange(EEPROM_SIZE / BYTES_AT_A_TIME): | |
byte0 = struct.unpack('>B', eeprom_template[count])[0] | |
byte1 = struct.unpack('>B', eeprom_template[count + 1])[0] | |
word = self.bytesToWord(byte1, byte0) # swap here for endian-ness | |
self.writeWordToEEPROM(index, word) | |
count += 2 | |
def bytesToWord(self, byte0, byte1): | |
''' | |
takes two individual bytes and makes a 16bit word | |
''' | |
return byte0 << 8 | byte1 | |
def wordToBytes(self, word): | |
''' | |
takes a 2 byte word and returns a tuple with two individual bytes | |
''' | |
byte0 = word >> 8 | |
byte1 = word & 0xff | |
return (byte0, byte1) | |
def computeChecksum(self, eeprom_template): | |
''' | |
compute the checksum to be written to eeprom based on eeprom template | |
eeprom template is a struct.packed' object, ie a list of strings where each | |
element is a byte in the eeprom. | |
''' | |
# http://stackoverflow.com/questions/25239423/crc-ccitt-16-bit-python-manual-calculation | |
# TODO this doesnt work right now, so come back to it. | |
# good thing that checksum is not necessary to functionality :-) | |
count = 0 | |
crc = 0xaaaa | |
while count < EEPROM_SIZE: | |
byte0 = struct.unpack('>B', eeprom_template[count])[0] | |
byte1 = struct.unpack('>B', eeprom_template[count + 1])[0] | |
word = self.bytesToWord(byte1, byte0) # swappadoodle | |
if((count >= 0 and count < 36) or (count >= 128 and count < 254)): | |
crc = crc ^ word | |
crc = (crc << 1) | (crc >> 15) | |
count += 2 | |
return crc | |
def restoreEEPROMFromFile(self, filename): | |
''' | |
given a filename, this function reads the hex dump of eeprom | |
from the file and then writes the file contents to eeprom on | |
the shikra device. | |
''' | |
try: | |
fd = open(filename, "r") | |
except: | |
print "[+] ERROR File \"{0}\" does not exist or permissions are wrong.".format(filename) | |
sys.exit(1) | |
print "[+] Reading EEPROM backup from {0}".format(filename) | |
temp_eeprom_array = [] | |
for line in fd: | |
line_tuple = line.strip().split(" ") | |
for element in line_tuple: | |
temp_eeprom_array.append(int(element, 16)) | |
print "[+] Writing EEPROM backup from {0} to Shikra".format(filename) | |
for index, element in enumerate(temp_eeprom_array): | |
self.writeWordToEEPROM(index, element) | |
fd.close() | |
def createEEPROMWriteTemplate(self): | |
''' | |
creates a eeprom template in the form of a packed struct and returns that. | |
This template can be fed to other methods to print the template or write | |
the template to eeprom on a shikra device's eeprom. | |
input - mode, defaults to RS232/UART mode. | |
This is where most of the 'magic' happens. This is where most of the bytes to | |
'bootstrap' the eeprom to a FT_PROG readable state happen here. This includes | |
setting the manufacturer, product, and serial number, as well as many other | |
configurations. | |
This is a little messy, but dealing with raw bytes is probably not the cleanest :P | |
''' | |
# the shikra eeprom uses 16bit word sizes, so therefore we have | |
# to write 2 "bytes" on each write since a byte here is 8 bits. | |
vendor_id = ID_VENDOR | |
product_id = ID_PRODUCT | |
release_number = USB_RELEASE_VERSION | |
bus_powered = True | |
remote_wakeup = False | |
current = 100 # draw of device in mAh | |
manufacturer_string = "XIPITER" | |
product_string = "SHIKRA" | |
serial_string = "XIP12345" | |
temp_eeprom_array = ["\x00" for x in xrange(EEPROM_SIZE)] # initialize with 0x0 bytes | |
# write shikra mode | |
temp_eeprom_array[0x1] = struct.pack('>B', 0) | |
temp_eeprom_array[0x0] = struct.pack('>B', self.mode) | |
# write vendor id | |
t = self.wordToBytes(vendor_id) | |
temp_eeprom_array[0x2] = struct.pack('>B', t[1]) | |
temp_eeprom_array[0x3] = struct.pack('>B', t[0]) | |
# write product id | |
t = self.wordToBytes(product_id) | |
temp_eeprom_array[0x4] = struct.pack('>B', t[1]) | |
temp_eeprom_array[0x5] = struct.pack('>B', t[0]) | |
# write release number | |
t = self.wordToBytes(release_number) | |
temp_eeprom_array[0x6] = struct.pack('>B', t[1]) | |
temp_eeprom_array[0x7] = struct.pack('>B', t[0]) | |
# set bus_powerd, remote_wakeup and current draw | |
byte0 = 0x0 | |
byte1 = 0x0 | |
if(not bus_powered): | |
byte0 = 0x40 | |
else: | |
byte0 = 0x80 | |
if(remote_wakeup): | |
byte1 = 0x20 | |
else: | |
byte1 = 0x0 | |
byte2 = byte0 + byte1 | |
temp = (current / 2) << 8 | byte2 | |
t = self.wordToBytes(temp) | |
temp_eeprom_array[0x8] = struct.pack('>B', t[1]) | |
temp_eeprom_array[0x9] = struct.pack('>B', t[0]) | |
# user programmable portion of 93C56 EEPROM starts at 0x14, so index 7 in our array | |
# first byte is len(manufacturer_string) at 0xf | |
# which tells us how long to parse the string | |
byte0 = (len(manufacturer_string) * 2) + 2 | |
# the second byte is at 0xe and tells us where the string begins | |
# user space is everything after 0x14, and string can start at 0x80 as base | |
# one byte needs to be reserved for ETX,NL | |
etx = 0x03 | |
start = 0x20 | |
base = 0x80 | |
temp_eeprom_array[base + start] = struct.pack('>B', byte0) | |
temp_eeprom_array[base + start + 1] = struct.pack('>B', etx) | |
start += 2 | |
byte1 = base + start # start of string for manufacturer | |
char_start = byte1 # address of first character of the string | |
temp_eeprom_array[0xf] = struct.pack('>B', byte0) | |
temp_eeprom_array[0xe] = struct.pack('>B', byte1) | |
# now we need to actually write the string to that location | |
count = 0 | |
for char in manufacturer_string: | |
addr = base + start + count | |
temp_eeprom_array[addr + 0] = struct.pack('>B', 0x00) # add Nul byte | |
temp_eeprom_array[addr] = struct.pack('>B', ord(char)) | |
count += 2 | |
char_start += len(manufacturer_string) * 2 | |
# now we have to set product string. | |
byte0 = (len(product_string) * 2) + 2 | |
temp_eeprom_array[char_start] = struct.pack('>B', byte0) | |
temp_eeprom_array[char_start + 1] = struct.pack('>B', etx) | |
char_start += 2 | |
byte1 = char_start | |
temp_eeprom_array[0x11] = struct.pack('>B', byte0) # set length of string | |
temp_eeprom_array[0x10] = struct.pack('>B', byte1) # set address of string | |
count = 0 | |
for char in product_string: | |
addr = char_start + count | |
temp_eeprom_array[addr] = struct.pack('>B', ord(char)) | |
count += 2 | |
char_start += len(product_string) * 2 | |
# now write out the serial number | |
# serial num = fixed 8-digit alphanumeric string | |
byte0 = (len(serial_string) * 2) + 2 | |
temp_eeprom_array[char_start] = struct.pack('>B', byte0) | |
temp_eeprom_array[char_start + 1] = struct.pack('>B', etx) | |
char_start += 2 | |
byte1 = char_start | |
temp_eeprom_array[0x13] = struct.pack('>B', byte0) # set strlen | |
temp_eeprom_array[0x12] = struct.pack('>B', byte1) # set address of string | |
count = 0 | |
for char in serial_string: | |
addr = char_start + count | |
temp_eeprom_array[addr] = struct.pack('>B', ord(char)) | |
count += 2 | |
char_start += len(serial_string) * 2 | |
# compute checksum TODO | |
temp_eeprom_array[254] = struct.pack('>B', 0xff) | |
temp_eeprom_array[255] = struct.pack('>B', 0xff) | |
# set eeprom hardware type | |
temp_eeprom_array[30] = struct.pack('>B', EEPROM_TYPE) | |
# configure the shikra's LED. | |
# LED on ACBUS9 or C9 depending on FT_PROG name | |
if(self.led_config is not None): | |
# led argument has been set | |
temp_eeprom_array[28] = struct.pack('>B', self.led_config) | |
return temp_eeprom_array | |
# lets just make the shikra device class global so we can stop trying to pass it around. This just makes things easier. | |
SHIKRA = Shikra() | |
shell = ShikraCLI() | |
shell.cmdloop() |