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