Created
January 16, 2020 10:23
-
-
Save raplin/46850439d4fcca8fefeee4ffa681ab67 to your computer and use it in GitHub Desktop.
Decode a saleae logic dump of an SPI-eeprom based boot process and extract the actual read eeprom contents
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
import struct,time | |
import re | |
class CaptureEnd(Exception): | |
pass | |
class Capture(object): | |
def __init__(self,fileName,pins,opts,startOffset=0): | |
self.f=open(fileName,"rb") | |
self.f.seek(startOffset) | |
self.opts=opts | |
self.pins=pins | |
self.buf="" | |
self.bufPos=0 | |
for k,v in pins.iteritems(): | |
self.__dict__[k]=0 | |
self.__dict__[k+"Pin"]=v | |
self.lastSample=None | |
self.eof=False | |
self.postInit() | |
def postInit(self): | |
pass | |
def sample(self): | |
sameCount=0 | |
while True: | |
#assumes bytes from saleae | |
if self.bufPos>=len(self.buf): | |
self.buf=self.f.read(0x10000) | |
#self.buf=(chr(0)*0xff00)+chr(1)+chr(2) | |
#print "\tread %x" % self.f.tell() | |
if len(self.buf)==0: | |
self.eof=True | |
return False | |
self.bufPos=0 | |
if self.buf[self.bufPos]==self.lastSample: | |
#complex but fast optimization to use regex to scan buffer for next changed byte | |
#this is vastly faster than python | |
sameCount+=1 | |
#..but only faster if we have a long run of same bytes, hence don't bother until we've seen 20 | |
if sameCount==20: | |
sameCount=0 | |
#fast way to look for next changed byte in buffer | |
ms=br'[^'+re.escape(self.lastSample)+']' | |
res = re.compile(br'[^'+re.escape(self.lastSample)+']') | |
match=res.search(self.buf,pos=self.bufPos) | |
if match: | |
self.bufPos=match.start() | |
break | |
else: | |
#skip rest of buffer | |
self.bufPos=len(self.buf) | |
else: | |
self.bufPos+=1 | |
else: | |
break | |
ls=self.buf[self.bufPos] | |
d=ord(ls) | |
self.lastSample=ls | |
self.bufPos+=1 | |
for k,v in self.pins.iteritems(): | |
self.__dict__[k]=1 if d & (1<<v) else 0 | |
return True | |
class SPICapture(Capture): | |
def postInit(self): | |
pass | |
def sniffSPITransaction(self): | |
mosi=[] | |
miso=[] | |
mosiByte=misoByte=0 | |
bits=0 | |
lastClk=0 | |
while self.sample(): | |
if self.CS==0: | |
break | |
while self.sample(): | |
if self.CS==1: | |
break | |
if self.CLK != lastClk: | |
if lastClk == self.opts["CPHA"]: | |
#msb first | |
if self.opts["MSBFirst"]: | |
mosiByte=(mosiByte<<1) | self.MOSI | |
misoByte=(misoByte<<1) | self.MISO | |
else: | |
mosiByte|=self.MOSI << bits | |
misoByte|=self.MISO << bits | |
bits+=1 | |
if bits==8: | |
mosi.append(mosiByte) | |
miso.append(misoByte) | |
mosiByte=misoByte=0 | |
bits=0 | |
lastClk=self.CLK | |
return miso,mosi | |
class Deeeprom(object): | |
EE_CMDS={ | |
0x01:"EE_CMD_WRITESTATUS", | |
0x02:"EE_CMD_PAGEWRITE", | |
0x05:"EE_CMD_READSTATUS", | |
0x06:"EE_CMD_WREN", | |
0x04:"EE_CMD_WRDIS", | |
0x03:"EE_CMD_READ", | |
0x0B:"EE_CMD_READ2", | |
0x1B:"EE_CMD_READ3", | |
0xd8:"EE_CMD_SECTOR_ERASE", | |
0xc7:"EE_CMD_BULK_ERASE", | |
0x9f:"EE_CMD_JEDEC_ID", | |
0xab:"EE_CMD_DEVICE_ID", | |
} | |
def __init__(self,size=1*1024*1024): | |
self.EE_SIZE=size | |
for k,v in self.EE_CMDS.iteritems(): | |
self.__dict__[v]=k | |
def sniff(self,capture,outFile): | |
PADDING=0x69 | |
dataMap=[PADDING] * self.EE_SIZE | |
maxAddr=0 | |
minAddr=1<<31 | |
while not capture.eof: | |
miso,mosi=capture.sniffSPITransaction() | |
if len(miso)==0: | |
continue | |
cmd=mosi[0] | |
ctxt="?" | |
if cmd in self.EE_CMDS: | |
ctxt=self.EE_CMDS[cmd] | |
print "CMD 0x%02x (%s)\tlen +0x%x" % (cmd,ctxt,len(miso)) | |
if (cmd==self.EE_CMD_READ or cmd==self.EE_CMD_READ2 or cmd==self.EE_CMD_READ3) and len(miso)>3: #read | |
addr3,addr2,addr1=mosi[1:4] | |
addr=(addr3<<16)|(addr2<<8)|addr1 | |
readPtr=4 | |
if cmd==self.EE_CMD_READ2: #has a don't care byte before read to allow extra access time | |
readPtr+=1 | |
elif cmd==self.EE_CMD_READ3: #two extra | |
readPtr+=2 | |
startAddr=addr | |
if addr<minAddr: | |
minAddr=addr | |
for n in xrange(readPtr,len(miso)): | |
dataMap[addr]=miso[n] | |
addr+=1 | |
if addr>maxAddr: | |
maxAddr=addr | |
print "\tRead 0x%x - 0x%x (+0x%x)" % (startAddr,addr,addr-startAddr) | |
elif cmd==self.EE_CMD_JEDEC_ID: | |
print "JEDEC ID 0x%02x 0x%02x 0x%02x" % (miso[1],miso[2],miso[3]) | |
elif cmd==self.EE_CMD_DEVICE_ID: | |
print "Device ID %02x" % (miso[4]) | |
readLen=maxAddr-minAddr | |
print "Total: Covered 0x%x - 0x%x (+0x%x) (%.1fMB, %d%%)" % (minAddr,maxAddr,readLen,readLen/(1024.0*1024),(readLen*100)/self.EE_SIZE) | |
with open(outFile,"wb") as f: | |
f.write("".join([chr(d) for d in dataMap])) | |
if False: | |
pins={"CLK":4, "CS":0, "MISO":1, "MOSI":3 } | |
opts={"CPOL":0, "CPHA":0,"MSBFirst":1} | |
fn="PA210_A_factory" | |
size=1*1024*1024 | |
fileName="../../../!LACaptures/%s.bin" % fn | |
else: | |
pins={"CLK":3, "CS":0, "MISO":1, "MOSI":2 } | |
opts={"CPOL":0, "CPHA":0,"MSBFirst":1} | |
fn="AR9331_boot" | |
size=16*1024*1024 | |
fileName="../../../!LACaptures/%s.bin" % fn | |
outFile="%s.eeprom" % fn | |
startOffset=0 #40239104 | |
s=SPICapture(fileName,pins,opts,startOffset=startOffset) | |
d=Deeeprom(size=size) | |
d.sniff(s,outFile) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment