358 lines
14 KiB
Python
358 lines
14 KiB
Python
|
from array import array
|
||
|
from construct.core import Struct, Union
|
||
|
from construct.macros import *
|
||
|
from progressbar import ProgressBar
|
||
|
from structs import *
|
||
|
import struct
|
||
|
|
||
|
|
||
|
#https://github.com/iDroid-Project/openiBoot/blob/master/openiboot/ftl-yaftl/yaftl.c
|
||
|
YAFTL_CXT = Struct("YAFTL_CXT",
|
||
|
String("version", 4),
|
||
|
ULInt32("unknCalculatedValue0"),
|
||
|
ULInt32("totalPages"),
|
||
|
ULInt32("latestUserBlock"),
|
||
|
ULInt32("cxt_unkn0_usn"),
|
||
|
ULInt32("latestIndexBlock"),
|
||
|
ULInt32("maxIndexUsn"),
|
||
|
ULInt32("blockStatsField4"),
|
||
|
ULInt32("blockStatsField10"),
|
||
|
ULInt32("numAllocatedBlocks"),
|
||
|
ULInt32("numIAllocatedBlocks"),
|
||
|
ULInt32("unk184_0xA"),
|
||
|
Array(10, ULInt32("cxt_unkn1")),
|
||
|
ULInt32("field_58"),
|
||
|
ULInt16("tocArrayLength"),
|
||
|
ULInt16("tocPagesPerBlock"),
|
||
|
ULInt16("tocEntriesPerPage"),
|
||
|
ULInt16("unkn_0x2A"),
|
||
|
ULInt16("userPagesPerBlock"),
|
||
|
ULInt16("unk64"),
|
||
|
Array(11, ULInt32("cxt_unkn2")),
|
||
|
ULInt8("unk188_0x63"),
|
||
|
)
|
||
|
|
||
|
TOCStruct = Struct("TOCStruct",
|
||
|
ULInt32("indexPage"),
|
||
|
ULInt16("cacheNum"),
|
||
|
ULInt16("TOCUnkMember2"),
|
||
|
)
|
||
|
|
||
|
BlockStats = Struct("BlockStats",
|
||
|
ULInt32("numAllocated"),
|
||
|
ULInt32("field_4"),
|
||
|
ULInt32("numValidDPages"),
|
||
|
ULInt32("numIAllocated"),
|
||
|
ULInt32("field_10"),
|
||
|
ULInt32("numValidIPages"),
|
||
|
ULInt32("numFree"),
|
||
|
ULInt32("field_1C"),
|
||
|
)
|
||
|
|
||
|
|
||
|
class YAFTL(object):
|
||
|
def __init__(self, vfl, usn=0):
|
||
|
self.vfl = vfl
|
||
|
self.lpnToVpn = None
|
||
|
bytesPerPage = vfl.nand.pageSize
|
||
|
numBlocks = vfl.context.usable_blocks_per_bank
|
||
|
self.blankPage = bytesPerPage * "\x00"
|
||
|
self.numBlocks = numBlocks
|
||
|
self.tocPagesPerBlock = vfl.pages_per_sublk * 4 / bytesPerPage
|
||
|
if vfl.pages_per_sublk * 4 % bytesPerPage:
|
||
|
self.tocPagesPerBlock += 1
|
||
|
self.tocEntriesPerPage = bytesPerPage / 4
|
||
|
self.tocArrayLength = CEIL_DIVIDE(vfl.pages_per_sublk * numBlocks * 4, bytesPerPage)
|
||
|
self.nPagesTocPageIndices = CEIL_DIVIDE(self.tocArrayLength * 4, bytesPerPage)
|
||
|
self.nPagesBlockStatuses = CEIL_DIVIDE(numBlocks * 1, bytesPerPage)
|
||
|
self.nPagesBlockReadCounts = CEIL_DIVIDE(numBlocks * 2, bytesPerPage)
|
||
|
self.nPagesBlockEraseCounts = CEIL_DIVIDE(numBlocks * 4, bytesPerPage)
|
||
|
self.nPagesBlockValidPagesDNumbers = self.nPagesBlockReadCounts
|
||
|
self.nPagesBlockValidPagesINumbers = self.nPagesBlockReadCounts
|
||
|
self.ctrlBlockPageOffset = self.nPagesTocPageIndices \
|
||
|
+ self.nPagesBlockStatuses \
|
||
|
+ self.nPagesBlockReadCounts \
|
||
|
+ self.nPagesBlockEraseCounts \
|
||
|
+ self.nPagesBlockValidPagesDNumbers \
|
||
|
+ self.nPagesBlockValidPagesINumbers \
|
||
|
+ 2 * self.tocPagesPerBlock \
|
||
|
+ 2
|
||
|
self.totalPages = (self.numBlocks - 8) * (self.vfl.pages_per_sublk - self.tocPagesPerBlock)# - unknCalculatedValue0
|
||
|
self.userPagesPerBlock = self.vfl.pages_per_sublk - self.tocPagesPerBlock
|
||
|
maxUsn = 0
|
||
|
ftlCtrlBlock = -1
|
||
|
for b in self.vfl.VFL_get_FTLCtrlBlock():
|
||
|
s,d = self.YAFTL_readPage(b * self.vfl.pages_per_sublk)
|
||
|
if not d:
|
||
|
continue
|
||
|
if usn and s.usn > usn:
|
||
|
break
|
||
|
if s.usn > maxUsn:
|
||
|
maxUsn = s.usn
|
||
|
ftlCtrlBlock = b
|
||
|
if ftlCtrlBlock == -1 or not maxUsn:
|
||
|
print "ftlCtrlBlock not found, restore needed"
|
||
|
self.YAFTL_restore()
|
||
|
return
|
||
|
i = 0
|
||
|
maxUsn = 0
|
||
|
while i < self.vfl.pages_per_sublk - self.ctrlBlockPageOffset:
|
||
|
s,d = self.YAFTL_readPage(ftlCtrlBlock*self.vfl.pages_per_sublk + i + self.ctrlBlockPageOffset)
|
||
|
if not d:
|
||
|
if self.YAFTL_readCxtInfo(ftlCtrlBlock*self.vfl.pages_per_sublk + i):
|
||
|
return
|
||
|
print "YaFTL_readCxtInfo FAIL, restore needed maxUsn=%d" % maxUsn
|
||
|
self.YAFTL_restore()
|
||
|
return
|
||
|
if s and s.usn > maxUsn:
|
||
|
maxUsn = s.usn
|
||
|
i += self.ctrlBlockPageOffset + 1
|
||
|
print "YaFTL open fail"
|
||
|
self.YAFTL_restore()
|
||
|
|
||
|
def readBTOCPages(self, block, maxVal):
|
||
|
data = ""
|
||
|
for i in xrange(self.tocPagesPerBlock):
|
||
|
s,d = self.YAFTL_readPage((block+1) * self.vfl.pages_per_sublk - self.tocPagesPerBlock + i)
|
||
|
if not s:
|
||
|
return None
|
||
|
data += d
|
||
|
btoc = array("I",data)
|
||
|
for i in xrange(len(btoc)):
|
||
|
if btoc[i] > maxVal:
|
||
|
btoc[i] = 0xFFFFFFFF
|
||
|
return btoc
|
||
|
|
||
|
def YAFTL_restore(self):
|
||
|
self.lpnToVpn = self.vfl.nand.loadCachedData("yaftlrestore")
|
||
|
if self.lpnToVpn:
|
||
|
print "Found cached FTL restore information"
|
||
|
return
|
||
|
userBlocks = {}
|
||
|
indexBlocks = {}
|
||
|
print "FTL restore in progress"
|
||
|
pbar = ProgressBar(self.numBlocks)
|
||
|
pbar.start()
|
||
|
for b in xrange(0, self.numBlocks):
|
||
|
pbar.update(b)
|
||
|
#read fist page in block, if empty then block is empty
|
||
|
s,d = self.YAFTL_readPage(b * self.vfl.pages_per_sublk + 0)
|
||
|
if not s:
|
||
|
continue
|
||
|
if s.type == PAGETYPE_INDEX:
|
||
|
indexBlocks[s.usn] = b
|
||
|
elif s.type == PAGETYPE_LBN:
|
||
|
if userBlocks.has_key(s.usn):
|
||
|
print "Two blocks with same USN, something is weird"
|
||
|
userBlocks[s.usn] = b
|
||
|
elif s.type == PAGETYPE_FTL_CLEAN:
|
||
|
pass
|
||
|
pbar.finish()
|
||
|
lpnToVpn = {}
|
||
|
for usn in sorted(userBlocks.keys(), reverse=True):
|
||
|
b = userBlocks[usn]
|
||
|
btoc = self.readBTOCPages(b, self.totalPages)
|
||
|
if btoc:
|
||
|
for i in xrange(self.userPagesPerBlock-1,-1, -1):
|
||
|
if not lpnToVpn.has_key(btoc[i]):
|
||
|
lpnToVpn[btoc[i]] = b * self.vfl.pages_per_sublk + i
|
||
|
else:
|
||
|
print "BTOC not found for block %d (usn %d), scanning all pages" % (b, usn)
|
||
|
i = 0
|
||
|
for p in xrange(self.vfl.pages_per_sublk - self.tocPagesPerBlock -1, -1, -1):
|
||
|
s,d = self.YAFTL_readPage(b * self.vfl.pages_per_sublk + p)
|
||
|
if s:
|
||
|
i+= 1
|
||
|
if s and not lpnToVpn.has_key(s.lpn):
|
||
|
lpnToVpn[s.lpn] = b * self.vfl.pages_per_sublk + p
|
||
|
print "%d used pages in block" % i
|
||
|
self.vfl.nand.cacheData("yaftlrestore", lpnToVpn)
|
||
|
self.lpnToVpn = lpnToVpn
|
||
|
return lpnToVpn
|
||
|
|
||
|
def YAFTL_readCxtInfo(self, page):
|
||
|
s,d = self.YAFTL_readPage(page)
|
||
|
if not s or s.type != PAGETYPE_FTL_CLEAN:
|
||
|
return False
|
||
|
ctx = YAFTL_CXT.parse(d)
|
||
|
ctx.spareUsn = s.usn
|
||
|
if ctx.version != "CX01":
|
||
|
print "Wrong FTL version %s" % ctx.version
|
||
|
return False
|
||
|
self.usn = s.usn
|
||
|
pageToRead = page + 1;
|
||
|
userTOCBuffer = self.YAFTL_read_n_Page(pageToRead, self.tocPagesPerBlock)
|
||
|
if not userTOCBuffer:
|
||
|
raise(Exception("userTOCBuffer"))
|
||
|
pageToRead += self.tocPagesPerBlock
|
||
|
indexTOCBuffer = self.YAFTL_read_n_Page(pageToRead, self.tocPagesPerBlock)
|
||
|
pageToRead += self.tocPagesPerBlock + 1
|
||
|
tocArrayIndexPages = self.YAFTL_read_n_Page(pageToRead, self.nPagesTocPageIndices)
|
||
|
self.tocArrayIndexPages = array("I", tocArrayIndexPages)
|
||
|
assert self.tocArrayIndexPages.itemsize == 4
|
||
|
self.indexCache = {}
|
||
|
pageToRead += self.nPagesTocPageIndices
|
||
|
|
||
|
if False: #we don't care, we just want to read
|
||
|
blockStatuses = self.YAFTL_read_n_Page(pageToRead, self.nPagesBlockStatuses)
|
||
|
pageToRead += self.nPagesBlockStatuses
|
||
|
blockReadCounts = self.YAFTL_read_n_Page(pageToRead, self.nPagesBlockReadCounts)
|
||
|
pageToRead += self.nPagesBlockReadCounts
|
||
|
blockEraseCounts = self.YAFTL_read_n_Page(pageToRead, self.nPagesBlockEraseCounts)
|
||
|
pageToRead += self.nPagesBlockEraseCounts
|
||
|
validPagesINo = self.YAFTL_read_n_Page(pageToRead, self.nPagesBlockValidPagesINumbers)
|
||
|
pageToRead += self.nPagesBlockValidPagesINumbers
|
||
|
validPagesDNo = self.YAFTL_read_n_Page(pageToRead, self.nPagesBlockValidPagesDNumbers)
|
||
|
|
||
|
print "YaFTL context OK, version=%s maxIndexUsn=%d context usn=%d" % (ctx.version, ctx.maxIndexUsn, self.usn)
|
||
|
return True
|
||
|
|
||
|
def YAFTL_read_n_Page(self, page, n, failIfBlank=False):
|
||
|
r = ""
|
||
|
for i in xrange(0, n):
|
||
|
s,d = self.YAFTL_readPage(page +i)
|
||
|
if not d:
|
||
|
if failIfBlank:
|
||
|
return
|
||
|
return r
|
||
|
r += d
|
||
|
return r
|
||
|
|
||
|
def YAFTL_readPage(self, page, key=META_KEY, lpn=None):
|
||
|
return self.vfl.read_single_page(page, key, lpn)
|
||
|
|
||
|
def build_lpn_to_vpn(self):
|
||
|
lpnToVpn = {}
|
||
|
for p in xrange(self.totalPages):
|
||
|
x = self.translateLPNtoVPN(p)
|
||
|
if x != 0xFFFFFFFF:
|
||
|
lpnToVpn[p] = x
|
||
|
self.vfl.nand.cacheData("currentftl", lpnToVpn)
|
||
|
return lpnToVpn
|
||
|
|
||
|
def translateLPNtoVPN(self, lpn):
|
||
|
if self.lpnToVpn:
|
||
|
return self.lpnToVpn.get(lpn, 0xFFFFFFFF)
|
||
|
tocPageNum = (lpn) / self.tocEntriesPerPage
|
||
|
indexPage = self.tocArrayIndexPages[tocPageNum]
|
||
|
if indexPage == 0xffffffff:
|
||
|
return 0xffffffff
|
||
|
#print "indexPage %x" % indexPage
|
||
|
if self.indexCache.has_key(indexPage):
|
||
|
tocPageBuffer = self.indexCache[indexPage]
|
||
|
else:
|
||
|
s,tocPageBuffer = self.YAFTL_readPage(indexPage)
|
||
|
if not tocPageBuffer:
|
||
|
print "tocPageBuffer fail"
|
||
|
return 0xffffffff
|
||
|
assert s.type == PAGETYPE_INDEX
|
||
|
tocPageBuffer = array("I", tocPageBuffer)
|
||
|
self.indexCache[indexPage] = tocPageBuffer
|
||
|
|
||
|
tocEntry = tocPageBuffer[lpn % self.tocEntriesPerPage]
|
||
|
return tocEntry
|
||
|
|
||
|
def readLPN(self, lpn, key=None):#, nPages):
|
||
|
vpn = self.translateLPNtoVPN(lpn)
|
||
|
if vpn == 0xffffffff:
|
||
|
return self.blankPage
|
||
|
#print "tocEntry %d" % tocEntry
|
||
|
#print "FTL %d => %d" % (lpn, vpn)
|
||
|
s,d = self.YAFTL_readPage(vpn, key, lpn)
|
||
|
if d == None:
|
||
|
return self.blankPage
|
||
|
if s.lpn != lpn:
|
||
|
raise Exception("YAFTL translation FAIL spare lpn=%d vs expected %d" % (s.lpn, lpn))
|
||
|
return d
|
||
|
|
||
|
def YAFTL_lookup1(self):
|
||
|
hax = self.vfl.nand.loadCachedData("YAFTL_lookup1")
|
||
|
if hax:
|
||
|
print "Found cached FTL lookup table"
|
||
|
return hax
|
||
|
userBlocks = {}
|
||
|
indexBlocks = {}
|
||
|
print "Building FTL lookup table v1"
|
||
|
pbar = ProgressBar(self.numBlocks)
|
||
|
pbar.start()
|
||
|
for b in xrange(0, self.numBlocks):
|
||
|
pbar.update(b)
|
||
|
#read fist page in block, if empty then block is empty
|
||
|
s,d = self.YAFTL_readPage(b * self.vfl.pages_per_sublk + 0)
|
||
|
if not s:
|
||
|
continue
|
||
|
if s.type == PAGETYPE_INDEX:
|
||
|
indexBlocks[s.usn] = b
|
||
|
elif s.type == PAGETYPE_LBN:
|
||
|
if userBlocks.has_key(s.usn):
|
||
|
print "Two blocks with same USN, something is weird"
|
||
|
userBlocks[s.usn] = b
|
||
|
elif s.type == PAGETYPE_FTL_CLEAN:
|
||
|
pass#print b, "ftl block"
|
||
|
pbar.finish()
|
||
|
lpnToVpn = {}
|
||
|
for usn in sorted(userBlocks.keys(), reverse=False):
|
||
|
b = userBlocks[usn]
|
||
|
btoc = self.readBTOCPages(b, self.totalPages)
|
||
|
#print usn, b
|
||
|
if btoc:
|
||
|
for i in xrange(self.userPagesPerBlock-1,-1, -1):
|
||
|
lpnToVpn.setdefault(btoc[i], []).append(b * self.vfl.pages_per_sublk + i)
|
||
|
else:
|
||
|
#print "btoc not found for block %d (usn %d), scanning all pages" % (b, usn)
|
||
|
i = 0
|
||
|
usn = -1
|
||
|
for p in xrange(self.vfl.pages_per_sublk - self.tocPagesPerBlock -1, -1, -1):
|
||
|
s,d = self.YAFTL_readPage(b * self.vfl.pages_per_sublk + p)
|
||
|
if not s:
|
||
|
break
|
||
|
i+= 1
|
||
|
if usn == -1:
|
||
|
usn = s.usn
|
||
|
if usn != s.usn:
|
||
|
#print "Two usns in same block %d %d" % (usn, s.usn)
|
||
|
usn = s.usn
|
||
|
lpnToVpn.setdefault(s.lpn, []).append(b * self.vfl.pages_per_sublk + p)
|
||
|
#print "%d used pages in block" % i
|
||
|
#self.vfl.nand.cacheData("YAFTL_lookup1", (lpnToVpn, userBlocks))
|
||
|
return lpnToVpn, userBlocks
|
||
|
|
||
|
def YAFTL_hax2(self):
|
||
|
hax = self.vfl.nand.loadCachedData("YAFTL_hax2")
|
||
|
if hax:
|
||
|
print "Found cached FTL HAX2 information"
|
||
|
return hax
|
||
|
|
||
|
print "FTL hax2 in progress"
|
||
|
pbar = ProgressBar(self.numBlocks)
|
||
|
pbar.start()
|
||
|
lpnToVpn = {}
|
||
|
for b in xrange(0, self.numBlocks):
|
||
|
pbar.update(b)
|
||
|
#read fist page in block, if empty then block is empty (right?)
|
||
|
s,d = self.YAFTL_readPage(b * self.vfl.pages_per_sublk + 0)
|
||
|
if not s:
|
||
|
continue
|
||
|
if s.type == PAGETYPE_LBN:
|
||
|
i = 0
|
||
|
usn = -1
|
||
|
for p in xrange(0, self.vfl.pages_per_sublk - self.tocPagesPerBlock):
|
||
|
s,d = self.YAFTL_readPage(b * self.vfl.pages_per_sublk + p)
|
||
|
if not s:
|
||
|
break
|
||
|
lpnToVpn.setdefault(s.lpn, {}).setdefault(s.usn, []).append(b * self.vfl.pages_per_sublk + p)
|
||
|
i+= 1
|
||
|
|
||
|
pbar.finish()
|
||
|
self.vfl.nand.cacheData("YAFTL_hax2", lpnToVpn)
|
||
|
return lpnToVpn
|
||
|
|
||
|
def block_lpn_to_vpn(self, block):
|
||
|
res = {}
|
||
|
for p in xrange(0, self.vfl.pages_per_sublk - self.tocPagesPerBlock):
|
||
|
s,d = self.YAFTL_readPage(block * self.vfl.pages_per_sublk + p)
|
||
|
if not s:
|
||
|
break
|
||
|
res[s.lpn] = block * self.vfl.pages_per_sublk + p
|
||
|
return res
|