Skip to content

Instantly share code, notes, and snippets.

@raplin
Created January 16, 2020 10:23
Show Gist options
  • Save raplin/46850439d4fcca8fefeee4ffa681ab67 to your computer and use it in GitHub Desktop.
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
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