├── LICENSE ├── setup.py ├── README.rst └── imxtools ├── imx_nand_info.py ├── imx_nand_convert.py ├── __init__.py └── fcb.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Econocom Digital.Security 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | # Utility function to read the README file. 5 | # Used for the long_description. It's nice, because now 1) we have a top level 6 | # README file and 2) it's easier to type in the README file than to put a raw 7 | # string in below ... 8 | def read(fname): 9 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 10 | 11 | setup( 12 | name = "imx-nand-tools", 13 | version = "1.0.3", 14 | python_requires='>3.5.2', 15 | url='https://github.com/DigitalSecurity/imx-nand-tools', 16 | author = "Damien Cauquil", 17 | author_email = "damien.cauquil@digital.security", 18 | description = ("Freescale i.MX NAND reverse tools"), 19 | long_description=read("README.rst"), 20 | license = "MIT", 21 | keywords = "imx freescale tool", 22 | packages=['imxtools'], 23 | install_requires=[ 24 | 'progressbar2', 25 | 'termcolor', 26 | 'bchlib == 0.14.0' 27 | ], 28 | entry_points= { 29 | 'console_scripts': [ 30 | 'imx-nand-info=imxtools.imx_nand_info:main', 31 | 'imx-nand-convert=imxtools.imx_nand_convert:main' 32 | ] 33 | }, 34 | ) 35 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | i.MX NAND Tools for Freescale i.MX NAND Dumps 2 | ============================================= 3 | 4 | Provides dedicated tools for IMX NAND dumps reverse-engineering: 5 | 6 | * **imx-nand-info**: parse the FCB and display useful information about the Flash structure 7 | * **imx-nand-convert**: parse the FCB and convert the actual dump into a memory-based image that can be processed with binwalk 8 | 9 | How to install 10 | ============== 11 | 12 | You can choose to either install *imx-nand-tools* from PyPi or from the source. 13 | 14 | PyPi install 15 | ------------ 16 | 17 | That's the most easiest way to install *imx-nand-tools*: 18 | 19 | :: 20 | 21 | $ sudo pip3 install imx-nand-tools 22 | 23 | 24 | It will install *imx-nand-tools* and all its dependencies. 25 | 26 | 27 | Install from source 28 | ------------------- 29 | 30 | The following commands will install *imx-nand-tools* from source. 31 | 32 | :: 33 | 34 | $ git clone https://github.com/DigitalSecurity/imx-nand-tools.git 35 | $ sudo pip install setuptools 36 | $ cd imx-nand-tools 37 | $ python setup.py build 38 | $ sudo python setup.py install 39 | 40 | And that's it 41 | 42 | 43 | How to use it 44 | ============= 45 | 46 | imx-nand-info 47 | ------------- 48 | 49 | This tool loooks for the first *Firmware Control Block* (FCB) contained in a dump and parse it and then 50 | displays its contents. 51 | 52 | :: 53 | 54 | $ imx-nand-info fresh-dump.bin 55 | 56 | 57 | imx-nand-convert 58 | ---------------- 59 | 60 | This tool converts a fresh i.MX NAND dump into a useable memory image: 61 | 62 | :: 63 | 64 | $ imx-nand-convert fresh-dump.bin converted-dump.bin 65 | 66 | 67 | The *-c* option enables error correction thanks to embedded ECC information, thus *imx-nand-convert* will be able to fix potential errors in the original NAND dump. 68 | 69 | 70 | -------------------------------------------------------------------------------- /imxtools/imx_nand_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | ___ __ ____ __ _____ _ 3 | |_ _| \/ \ \/ /_|_ _|__ ___| |___ 4 | | || |\/| |> <___|| |/ _ \/ _ \ (_-< 5 | |___|_| |_/_/\_\ |_|\___/\___/_/__/ 6 | 7 | IMX Nand Information tool 8 | ========================= 9 | 10 | This tool 11 | 12 | 13 | """ 14 | 15 | from argparse import ArgumentParser 16 | from imxtools import parse_fcb, find_fcb_offset 17 | from termcolor import colored 18 | 19 | def main(): 20 | parser = ArgumentParser() 21 | parser.add_argument('-v', '--verbose', action='store_true', default=False, dest='verbose', help='Be more verbose') 22 | parser.add_argument('-o', '--offset', dest='offset', help='Force FCB offset value') 23 | parser.add_argument('nand_dump', type=str, help='Raw NAND dump to process') 24 | args = parser.parse_args() 25 | 26 | print(colored(''' 27 | ___ __ ____ __ _____ _ 28 | |_ _| \/ \ \/ /_|_ _|__ ___| |___ 29 | | || |\/| |> <___|| |/ _ \/ _ \ (_-< 30 | |___|_| |_/_/\_\ |_|\___/\___/_/__/ 31 | 32 | ---< IMX Nand Info >--- 33 | ''', 'cyan', attrs=['bold'])) 34 | 35 | # Load file 36 | if args.verbose: 37 | print('>> Loading memory dump ...') 38 | dump = open(args.nand_dump, 'rb').read(4096) 39 | 40 | # Override offset if provided 41 | if args.offset is not None: 42 | # Accept decimal and hexadecimal offset values 43 | if args.offset.lower().startswidth('0x'): 44 | offset = int(args.offset, 16) 45 | else: 46 | offset = int(args.offset) 47 | if args.verbose: 48 | print('>> Forcing FCB offset to %x' % offset) 49 | else: 50 | offset = find_fcb_offset(dump) 51 | 52 | if offset < 0: 53 | print('!!'+colored('FCB not found, check your dump.', 'red', attrs=['bold'])) 54 | else: 55 | if args.verbose: 56 | print('>> Dumping FCB details ...') 57 | # Display NAND info 58 | fcb = dump[offset:offset+140] 59 | parse_fcb(fcb, verbosity=args.verbose, display=True) 60 | -------------------------------------------------------------------------------- /imxtools/imx_nand_convert.py: -------------------------------------------------------------------------------- 1 | """ 2 | ___ __ ____ __ _____ _ 3 | |_ _| \/ \ \/ /_|_ _|__ ___| |___ 4 | | || |\/| |> <___|| |/ _ \/ _ \ (_-< 5 | |___|_| |_/_/\_\ |_|\___/\___/_/__/ 6 | 7 | IMX Nand Conversion tool 8 | ========================= 9 | 10 | """ 11 | 12 | from argparse import ArgumentParser 13 | from imxtools import convert_nand_dump, find_fcb_offset, extract_firmware 14 | from imxtools.fcb import FCB 15 | from termcolor import colored 16 | 17 | 18 | def main(): 19 | parser = ArgumentParser() 20 | parser.add_argument('-o', '--offset', dest='offset', help='Force FCB offset value') 21 | parser.add_argument('-b', '--bad-block-offset', dest='bb_offset', help='Force bad block marker offset') 22 | parser.add_argument('-p', '--page-size', dest='page_size', help='Force page size (in bytes)') 23 | parser.add_argument('-m', '--metadata-size', dest='metadata_size', help='Force metadata size (in bytes)') 24 | parser.add_argument('-e', '--ecc-size', dest='ecc_size', help='Force ECC size (in bits)') 25 | parser.add_argument('-v', '--verbose', action='store_true', default=False, dest='verbose', help='Be more verbose') 26 | parser.add_argument('-f', '--firmware', dest='firmware', type=int, help='Firmware number to extract (default: 1)') 27 | parser.add_argument('-c', '--correct', dest='ecc', action='store_true', default=False, help='Correct errors with ECC') 28 | parser.add_argument('nand_dump', type=str, help='Raw NAND dump to process') 29 | parser.add_argument('output_nand_dump', type=str, help='Output NAND dump') 30 | args = parser.parse_args() 31 | 32 | print(colored(''' 33 | ___ __ ____ __ _____ _ 34 | |_ _| \/ \ \/ /_|_ _|__ ___| |___ 35 | | || |\/| |> <___|| |/ _ \/ _ \ (_-< 36 | |___|_| |_/_/\_\ |_|\___/\___/_/__/ 37 | 38 | ---< IMX Nand Convert >--- 39 | ''', 'cyan', attrs=['bold'])) 40 | 41 | # Load file 42 | if args.verbose: 43 | print('>> Loading memory dump ...') 44 | dump = open(args.nand_dump, 'rb').read() 45 | 46 | # Override offset if provided 47 | if args.offset is not None: 48 | # Accept decimal and hexadecimal offset values 49 | if args.offset.lower().startswidth('0x'): 50 | offset = int(args.offset, 16) 51 | else: 52 | offset = int(args.offset) 53 | else: 54 | offset = find_fcb_offset(dump) 55 | 56 | if offset < 0: 57 | print('!!'+colored('FCB not found, check your dump.', 'red', attrs=['bold'])) 58 | else: 59 | if args.firmware is not None: 60 | if args.firmware in [1,2]: 61 | print('>> FCB found at offset 0x%08x' % offset) 62 | print('>> Extracting firmware #%d ...' % args.firmware) 63 | 64 | # Parse FCB 65 | fcb = FCB(dump[offset:offset+140]) 66 | 67 | extract_firmware(dump, fcb, args.output_nand_dump, args.firmware, args.bb_offset, args.metadata_size, args.page_size, args.ecc, args.ecc_size) 68 | else: 69 | print(colored('>> Firmware index MUST be 1 OR 2.', 'red', attrs=['bold'])) 70 | else: 71 | print('>> FCB found at offset 0x%08x' % offset) 72 | print('>> Converting image ...') 73 | # Display NAND info 74 | fcb = FCB(dump[offset:offset+140]) 75 | convert_nand_dump(dump, fcb, args.output_nand_dump, args.bb_offset, args.metadata_size, args.page_size, args.ecc, args.ecc_size) 76 | -------------------------------------------------------------------------------- /imxtools/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | IMX Tools main module 3 | 4 | @author Damien "virtualabs" Cauquil 5 | """ 6 | import sys 7 | import os.path 8 | import bchlib 9 | import progressbar 10 | 11 | from math import ceil 12 | from struct import unpack 13 | from argparse import ArgumentParser 14 | 15 | from termcolor import colored 16 | from imxtools.fcb import FCB, FCBError 17 | 18 | class BCH: 19 | instance = None 20 | 21 | @staticmethod 22 | def get_instance(ecc_strength): 23 | if BCH.instance is None: 24 | BCH.instance = bchlib.BCH(8219, ecc_strength, reverse=True) 25 | return BCH.instance 26 | 27 | def skip_bits(page, nbits, nbytes): 28 | """ 29 | Skip bits for a given page 30 | """ 31 | # compute relative shifting and complementary shifting values 32 | rel_shift = int(nbits%8) 33 | comp_shift= int(8 - rel_shift) 34 | 35 | # skip nbits/8 bytes first 36 | page = page[int(nbits/8):] 37 | page += bytes([0]) 38 | 39 | if rel_shift > 0: 40 | # Loop over bytes and shift rel_shift bits to the left 41 | output = [] 42 | for i in range(nbytes): 43 | output.append( 44 | (page[i]>>rel_shift) | ((page[i+1] << comp_shift)&0xff) 45 | ) 46 | else: 47 | output = page 48 | return output 49 | 50 | def parse_fcb(content, verbosity=None, display=False): 51 | """ 52 | Parse and display the FCB structure. 53 | """ 54 | fcb = FCB(content) 55 | 56 | # Display FCB if required 57 | if display: 58 | fcb.display() 59 | 60 | # return FCB info 61 | return fcb 62 | 63 | 64 | def ecc_correct(block, code, ecc_strength): 65 | """ 66 | Try to fix `block` with `code`, for an ECC size of `bitsize`. 67 | """ 68 | try: 69 | block_ = BCH.get_instance(ecc_strength).correct(bytes(block), bytes(code)) 70 | return block_ 71 | except Exception as e: 72 | return block 73 | 74 | 75 | def process_page(page, fcb, ecc=False): 76 | """ 77 | Split page in blocks and ecc codes. 78 | """ 79 | # First, remove metadata bytes 80 | page_size = len(page) 81 | marker = page[0] 82 | #assert(page[fcb.marker_raw_offset]==0xff) 83 | page = page[:fcb.marker_raw_offset] + bytes([marker]) + page[fcb.marker_raw_offset+1:] 84 | page = page[fcb.metadata_bytes:] 85 | 86 | blocks = [] 87 | nb_blocks = fcb.nb_ecc_blocks_per_page+1 88 | 89 | # Iterate over each block 90 | n = 8 91 | for i in range(nb_blocks): 92 | # First block is processed separately 93 | if i==0: 94 | ecc_size = fcb.get_ecc_block0_size() 95 | ecc_strength = fcb.get_ecc_block0_strength() 96 | block_size = fcb.get_data_block0_size() 97 | else: 98 | ecc_size = fcb.get_ecc_blockN_size() 99 | ecc_strength = fcb.get_ecc_blockN_strength() 100 | block_size = fcb.get_data_blockN_size() 101 | 102 | ecc_nb_bytes = int(ecc_size/8) 103 | if (fcb.get_ecc_block0_size()%8 > 0): 104 | ecc_nb_bytes += 1 105 | ecc = page[block_size:block_size+ecc_nb_bytes] 106 | 107 | 108 | # copy block_size bytes 109 | block = page[:block_size] 110 | 111 | # try to correct block if required 112 | if ecc: 113 | block = ecc_correct(block, ecc, ecc_strength*2) 114 | 115 | # save block 116 | blocks.append(block) 117 | 118 | # skip ecc_size (in bits) bits 119 | page = skip_bits(page, block_size*8 + ecc_size, int(((block_size*8 + ecc_size)*(n-1))/8)) 120 | n -= 1 121 | 122 | # Align to original page size 123 | output = [] 124 | for block in blocks: 125 | output.extend(block) 126 | return bytes(output) 127 | 128 | def extract_firmware(content, fcb, output, firmware_id, bb_marker_override=None, metadata_override=None, page_size_override=None, correct_ecc=False, ecc_size_override=None): 129 | """ 130 | Convert an IMX NAND dump to memory dump. 131 | """ 132 | # Apply overrides if defined 133 | if metadata_override is not None: 134 | fcb.set_metadata_bytes(metadata_override) 135 | if page_size_override is not None: 136 | fcb.set_page_data_size(page_size_override) 137 | if ecc_size_override is not None: 138 | fcb.set_ecc_size(ecc_size_override) 139 | if bb_marker_override is not None: 140 | fcb.set_bb_marker(bb_marker_override) 141 | 142 | # We keep only a fraction of the content, as described in the FCB 143 | if firmware_id == 1: 144 | content = content[fcb.fw1_start * fcb.total_page_size:(fcb.fw1_start + fcb.pages_fw1) * fcb.total_page_size] 145 | elif firmware_id == 2: 146 | content = content[fcb.fw2_start * fcb.total_page_size:(fcb.fw2_start + fcb.pages_fw2) * fcb.total_page_size] 147 | 148 | blocksize = fcb.total_page_size 149 | globsize = len(content) 150 | nbblocks = int(globsize/blocksize) 151 | 152 | valid_blocks = 0 153 | bad_blocks = 0 154 | output = open(output,'wb') 155 | bar = progressbar.ProgressBar(max_value=nbblocks) 156 | for i in range(nbblocks): 157 | bar.update(i) 158 | page = content[i*blocksize:(i+1)*blocksize] 159 | c_block = process_page(page, fcb, correct_ecc) 160 | output.write(c_block) 161 | output.close() 162 | 163 | def convert_nand_dump(content, fcb, output, bb_marker_override=None, metadata_override=None, page_size_override=None, correct_ecc=False, ecc_size_override=None): 164 | """ 165 | Convert an IMX NAND dump to memory dump. 166 | """ 167 | # Apply overrides if defined 168 | if metadata_override is not None: 169 | fcb.set_metadata_bytes(metadata_override) 170 | if page_size_override is not None: 171 | fcb.set_page_data_size(page_size_override) 172 | if ecc_size_override is not None: 173 | fcb.set_ecc_size(ecc_size_override) 174 | if bb_marker_override is not None: 175 | fcb.set_bb_marker(bb_marker_override) 176 | 177 | blocksize = fcb.total_page_size 178 | globsize = len(content) 179 | nbblocks = int(globsize/blocksize) 180 | 181 | valid_blocks = 0 182 | bad_blocks = 0 183 | output = open(output,'wb') 184 | bar = progressbar.ProgressBar(max_value=nbblocks) 185 | for i in range(nbblocks): 186 | bar.update(i) 187 | page = content[i*blocksize:(i+1)*blocksize] 188 | c_block = process_page(page, fcb, correct_ecc) 189 | output.write(c_block) 190 | output.close() 191 | 192 | 193 | def find_fcb_offset(content): 194 | """ 195 | Find Flash Control Block offset 196 | 197 | @return int offset >=0 on success, -1 if an error occured. 198 | """ 199 | try: 200 | index = content.index(b'FCB ') 201 | if (index > 4): 202 | return (index-4) 203 | return -1 204 | except ValueError as exc: 205 | return -1 206 | -------------------------------------------------------------------------------- /imxtools/fcb.py: -------------------------------------------------------------------------------- 1 | """ 2 | IMX Flash Control Block module 3 | 4 | This module parses and allow manipulation of the Flash Control Block. 5 | 6 | @author Damien Cauquil 7 | """ 8 | 9 | from math import ceil 10 | from struct import unpack 11 | from termcolor import colored 12 | 13 | def formatval(v): 14 | """ 15 | Format value string. 16 | """ 17 | return colored(v, 'cyan', attrs=['bold']) 18 | 19 | def u32le(b, offset): 20 | """ 21 | Reads a LE unsigned integer from `b` at the given `offset`. 22 | """ 23 | return unpack('' % self.message 43 | 44 | 45 | class FCB(object): 46 | """ 47 | Flash Control Block class 48 | """ 49 | 50 | def __init__(self, content): 51 | """ 52 | Constructor, parses the FCB content. 53 | """ 54 | if len(content) >= 132: 55 | try: 56 | # Unpack all the fields 57 | self.magic = content[4:8] 58 | if self.magic != b'FCB ': 59 | raise FCBError('Wrong FCB magic value (%08x instead of %08x)' % ( 60 | unpack('I', content[8:12])[0] 64 | self.page_data_size = u32le(content, 20) 65 | self.total_page_size = u32le(content, 24) 66 | self.sectors_per_block = u32le(content, 28) 67 | self.nb_nands = u32le(content, 32) 68 | self.ecc_block_type = u32le(content, 44) 69 | self.ecc_block0size = u32le(content, 48) 70 | self.ecc_blockNsize = u32le(content, 52) 71 | self.ecc_block0type= u32le(content, 56) 72 | self.metadata_bytes = u32le(content, 60) 73 | self.nb_ecc_blocks_per_page = u32le(content, 64) 74 | self.fw1_start = u32le(content, 104) 75 | self.fw2_start = u32le(content, 108) 76 | self.pages_fw1 = u32le(content, 112) 77 | self.pages_fw2 = u32le(content, 116) 78 | self.bch_type = u32le(content, 136) 79 | self.bb_marker = u32le(content, 124) 80 | self.bb_marker_bits = u32le(content, 128) 81 | 82 | # We compute the raw marker offset 83 | self.marker_page = int(self.bb_marker/self.ecc_blockNsize) 84 | self.marker_offset = self.bb_marker % self.ecc_blockNsize 85 | self.marker_raw_offset = int(self.metadata_bytes + ceil((self.ecc_block0type*26)/8) + ceil((self.marker_page-1)*(self.ecc_block_type*26)/8) + self.bb_marker) 86 | except TypeError as type_exc: 87 | raise FCBError("Content MUST be bye of type `bytes`") 88 | else: 89 | raise FCBError("FCB content must contain at least 132 bytes") 90 | 91 | def get_ecc_block0_size(self): 92 | """ 93 | Get the first block ECC size in bits. 94 | 95 | @return int ECC size in bits 96 | """ 97 | return (self.ecc_block0type * 26) 98 | 99 | def get_ecc_block0_strength(self): 100 | """ 101 | Get the first block ECC strength (BCH). 102 | 103 | @return int ECC strength (BCH) 104 | """ 105 | return (self.ecc_block0type) 106 | 107 | def get_ecc_blockN_size(self): 108 | """ 109 | Get the next blocks ECC size in bits. 110 | 111 | @return int ECC size in bits 112 | """ 113 | return (self.ecc_block_type * 26) 114 | 115 | def get_ecc_blockN_strength(self): 116 | """ 117 | Get the next blocks ECC strength (BCH). 118 | 119 | @return int ECC strength (BCH) 120 | """ 121 | return (self.ecc_block_type) 122 | 123 | def get_data_block0_size(self): 124 | """ 125 | Get first block data size on which the ECC is computed. 126 | 127 | @return int data block size 128 | """ 129 | return self.ecc_block0size 130 | 131 | def get_data_blockN_size(self): 132 | """ 133 | Get next blocks data size on which the ECC is computed. 134 | 135 | @return int data block size 136 | """ 137 | return self.ecc_blockNsize 138 | 139 | def set_metadata_bytes(self, metadata_bytes): 140 | """ 141 | Force metadata bytes size. 142 | 143 | @param metadata_bytes int Metadata bytes size 144 | """ 145 | self.metadata_bytes = metadata_bytes 146 | 147 | def set_page_data_size(self, page_data_size): 148 | """ 149 | Set page data size. 150 | 151 | @param page_data_size int Page size in bytes 152 | """ 153 | self.page_data_size = page_data_size 154 | 155 | def set_ecc_size(self, ecc_size): 156 | """ 157 | Set ECC size in bits. 158 | 159 | @param ecc_size int ECC size in bits. 160 | """ 161 | self.ecc_block0type = int(ecc_size/26) 162 | self.ecc_block_type = int(ecc_size/26) 163 | 164 | def set_bb_marker(self, bb_marker): 165 | """ 166 | Force bad block marker offset. 167 | 168 | @param bb_marker chr Bad block marker offset to use. 169 | """ 170 | self.bb_marker = bb_marker 171 | # We compute the raw marker offset 172 | self.marker_page = int(self.bb_marker/self.ecc_blockNsize) 173 | self.marker_offset = self.bb_marker % self.ecc_blockNsize 174 | self.marker_raw_offset = int(self.metadata_bytes + ceil((self.ecc_block0type*26)/8) + ceil((self.marker_page-1)*(self.ecc_block_type*26)/8) + self.bb_marker) 175 | 176 | def display(self): 177 | """ 178 | Display FCB to standard output. 179 | """ 180 | # Display information 181 | print('FCB version: %d' % self.version) 182 | print('') 183 | print('---[ NAND structure ]---------') 184 | print(' > Page data size:\t'+ formatval('%d bytes'% self.page_data_size)) 185 | print(' > Total page size:\t' + formatval('%d bytes (OOB: %d bytes)' % (self.total_page_size, self.total_page_size - self.page_data_size))) 186 | print(' > Sectors/block:\t' + formatval('%d' % self.sectors_per_block)) 187 | print(' > Number of Nands:\t' + formatval('%d' % self.nb_nands)) 188 | print('') 189 | print('---[ ECC ]--------------------') 190 | print(' > ECC block 0 type:\t' + formatval('%d (%d bits)' % (self.ecc_block0type, self.ecc_block0type*26))) 191 | print(' > ECC block 0 size:\t' + formatval('%d bytes' % self.ecc_block0size)) 192 | print(' > ECC block N type:\t' + formatval('%d (%d bits)' % (self.ecc_block_type, self.ecc_block_type*26))) 193 | print(' > ECC block N size:\t' + formatval('%d bytes' % self.ecc_blockNsize)) 194 | print(' > Metadata bytes:\t' + formatval('%d' % self.metadata_bytes)) 195 | # Original NumEccBlocksPerPage does not include Block0 196 | print(' > ECC blocks/page:\t' + formatval('%d' % (self.nb_ecc_blocks_per_page +1))) 197 | print(' > ECC BCH Type:\t%d' % self.bch_type) 198 | print('') 199 | print('---[ BadBlocks ]--------') 200 | print(' > Bad block marker byte:\t' + formatval('0x%x' % self.bb_marker)) 201 | print(' > Bad block start bit:\t\t' + formatval('0x%x' % self.bb_marker_bits)) 202 | print(' > Bad block Marker raw offset:\t' + formatval('0x%x' % self.marker_raw_offset)) 203 | print('') 204 | print('---[ Firmware Info]-----') 205 | print(' > Firmware #1:\t' + formatval('start @%08x (%d pages, %d bytes)' % (self.fw1_start, self.pages_fw1, self.pages_fw1*self.page_data_size))) 206 | print(' > Firmware #2:\t' + formatval('start @%08x (%d pages, %d bytes)' % (self.fw2_start, self.pages_fw2, self.pages_fw1*self.page_data_size))) 207 | --------------------------------------------------------------------------------