diff --git a/README.md b/README.md index 5f29578..c6b2f5f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# UniFlash: an opensource Unisoc/Spreadtrum phone flash reader/writer +# UniFlash: an opensource Unisoc/Spreadtrum feature phone flash reader/writer ## About @@ -24,23 +24,43 @@ Run `python uniflash.py -h` to see all parameters. But there are several typical **Note**: you need to hold a bootkey pressed when connecting the device for it to be detected correctly. This key can vary across devices. Typically it's Call, Soft Left, Soft Right, Center, 9 or #, but it can be anything else. +Some chipset versions (esp. the ones before SC6531E) require the following procedure to connect in order to initiate successful data transfer: + +1. Pull out the battery and the cable. +2. Insert the cable. +3. Hold the bootkey and then insert the battery. + +UniFlash ships with several typical targets that you can specify with `-t` (`--target`) parameter and not have to configure anything else: + +- `sc6531efm_generic` (default) - for SC6531E/F/M with no signed FDLs; +- `sc6530_generic` - for SC6530 and SC6531B/C/DA with no signed FDLs; +- `sc7703a_generic` - for SC7703A with no signed FDLs. + +If you want to, the targets are fully overridable with individual parameters, see program help for all details. + ### SC6531E -This is the default target for UniFlash, FDLs for it are shipped in the repo and you don't need to configure anything else unless your model requires signed FDLs. +This is the default target for UniFlash, FDLs for it are shipped in the repo and you don't need to configure anything (even `-t` parameter) else unless your model requires signed FDLs. For SC6531E, firmware is usually 4MB long, so you just need to run `python uniflash.py dump your-output-file.bin` to dump the firmware image and `python uniflash.py flash your-input-file.bin` to flash it. ### SC6531F/M -For SC6531F or SC6531M, **specifying the wrong dump length and attempts to write flash from the same dump can make the device unbootable!** +For SC6531F or SC6531M, the target is still `sc6531efm_generic` unless your model uses signed FDLs. However, **specifying the wrong dump length and attempts to write flash from the same dump can make the device unbootable!** Here, firmware can be larger, so you need to pass in the length. As with any integer parameter here, you can pass it in hexadecimal format as well. E.g. for CAT B26 (bootkey is `#`) the dumping command is: `python uniflash.py -l 0x800000 dump b26.bin` +### SC6531C + +SC6531C is a single-FDL variant. Here, if your model doesn't require signed FDLs, you need to specify `sc6530_generic` target and typically 8MB length, for instance, for Alcatel OT-2051D the boot key is Call key and the command to dump is: `python uniflash.py -t sc6530_generic -l 0x800000 dump ot2051d.bin` + +**Note**: for SC6531C, only readback is supported as of now. + ### SC6531D and lower -For now, SC6531DA and other single-FDL variants of SC6531 chipset are not supported. +For now, SC6531DA and other variants of SC6531 chipset older than SC6531CA are not supported. ## Credits diff --git a/unicmd.py b/unicmd.py index ee985a0..fb12553 100644 --- a/unicmd.py +++ b/unicmd.py @@ -263,5 +263,8 @@ def cmd_erase_flash(targetAddr, targetLen): datahdr = pack('>LL', targetAddr, targetLen) return shape_data_packet(BSL_CMD_ERASE_FLASH, datahdr) +def cmd_enable_write_flash(): + return shape_cmd_packet(BSL_CMD_ENABLE_WRITE_FLASH) + def cmd_end_flash_process(): return shape_cmd_packet(BSL_CMD_END_PROCESS) diff --git a/uniflash.py b/uniflash.py index 301fcc9..72656cd 100644 --- a/uniflash.py +++ b/uniflash.py @@ -82,6 +82,8 @@ def send_file_to_addr(fname, faddr, fdlBooted = False, flashMode = False, fbs = resp = reqresp(unicmd.cmd_data_start(faddr, flen, dataCrc), fdlBooted) rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted) assert rcode == unicmd.BSL_REP_ACK, 'Could not start data transfer, response code is %X' % rcode + if rcode == unicmd.BSL_REP_LOG: + print(r) print('Starting data transfer...') while fdata: buf = fdata[:pSize] @@ -148,30 +150,33 @@ def write_flash_mem(infile, offset, blocksize, forceErase): erase_flash_mem(flen, startAddr) send_file_to_addr(fdata, startAddr, True, True, blocksize) +# main code start + def auto_int(x): return int(x,0) -# main code start - if __name__ == '__main__': # main app start from argparse import ArgumentParser rootdir = os.path.dirname(os.path.realpath(__file__)) - parser = ArgumentParser(description='UniFlash: an opensource Unisoc SC6531E/F/M phone flash reader/writer', epilog='(c) Luxferre 2021 --- No rights reserved ') + parser = ArgumentParser(description='UniFlash: an opensource Unisoc/Spreadtrum feature phone flash reader/writer', epilog='(c) Luxferre 2021 --- No rights reserved ') parser.add_argument('mode', help='Operation mode (flash/dump)') parser.add_argument('file', help='File to read the flash data from or write the dump into') parser.add_argument('-p','--partid', type=auto_int, default=0x80000003, help='partition ID for readback (defaults to 0x80000003 that can address full flash space on SC6531E/F/M)') parser.add_argument('-s','--start', type=auto_int, default=0, help='start position (in the partition, defaults to 0)') parser.add_argument('-l', '--length', type=auto_int, default=0x400000, help='data length in bytes to read/write, defaults to 0x400000') - parser.add_argument('-e','--force-erase', type=bool, default=False, help='Erase target flash memory area before writing') + parser.add_argument('-t','--target', default='sc6531efm_generic', help='Preinstalled target (defaults to sc6531efm_generic, overridable with individual FDL parameters)') + parser.add_argument('-nr','--flash-noremap', action='store_true', help='Disable base address remapping for flashing') + parser.add_argument('-e','--force-erase', action='store_true', help='Erase target flash memory area before flashing') + parser.add_argument('-wf','--enable-write-flash', action='store_true', help='Send the write flash enable command before flashing (if necessary and supported)') parser.add_argument('-bs','--block-size', type=auto_int, default=4096, help='Readback/write block size (in bytes), defaults to 4096') parser.add_argument('-dv','--device-vid', type=auto_int, default=UNISOC_VID, help='Override device vendor ID') parser.add_argument('-dp','--device-pid', type=auto_int, default=UNISOC_PID, help='Override device product ID') - parser.add_argument('-fdl1','--fdl1-file', default=rootdir+'/fdls/sc6531efm_generic_0x40004000_fdl1.bin', help='Path to FDL1, defaults to the generic SC6531E/F/M FDL1 shipped with UniFlash') - parser.add_argument('-addr1','--fdl1-addr', type=auto_int, default=0x40004000, help='Address to load FDL1 into, defaults to 0x40004000') - parser.add_argument('-fdl2','--fdl2-file', default=rootdir+'/fdls/sc6531efm_generic_0x14000000_fdl2.bin', help='Path to FDL2, defaults to the generic SC6531E/F/M FDL2 shipped with UniFlash') - parser.add_argument('-addr2','--fdl2-addr', type=auto_int, default=0x14000000, help='Address to load FDL2 into, defaults to 0x14000000') - parser.add_argument('-sfdl','--single-fdl-file', default=None, help='Path to a single FDL (for old Spreadtrum chipsets)') - parser.add_argument('-saddr','--single-fdl-addr', type=auto_int, default=0x34000000, help='Address to load the single FDL into, defaults to 0x34000000') + parser.add_argument('-fdl1','--fdl1-file', default=None, help='Path to FDL1, overrides the target') + parser.add_argument('-addr1','--fdl1-addr', type=auto_int, default=None, help='Address to load FDL1 into, overrides the target') + parser.add_argument('-fdl2','--fdl2-file', default=None, help='Path to FDL2, overrides the target') + parser.add_argument('-addr2','--fdl2-addr', type=auto_int, default=None, help='Address to load FDL2 into, overrides the target') + parser.add_argument('-sfdl','--single-fdl-file', default=None, help='Path to a single FDL (for old Spreadtrum chipsets), overrides the target') + parser.add_argument('-saddr','--single-fdl-addr', type=auto_int, default=None, help='Address to load the single FDL into, overrides the target') args = parser.parse_args() @@ -179,24 +184,61 @@ if __name__ == '__main__': # main app start if args.mode == 'flash': is_flash = True + # parse target and resolve the parameters from it first + paramdelim = '_' + target = args.target + paramdelim + fdlDir = rootdir + '/fdls' + fdlList = [] + for root, dirs, files in os.walk(fdlDir): + for name in files: + if name.startswith(target): + paramstr = os.path.splitext(name)[0].split(target)[1] + params = paramstr.split(paramdelim) + fdlList.append((params[1], params[0], fdlDir+'/'+name)) + # resulting fdl list: (tag, address, path) + fdlSingleName = None + fdlSingleAddr = None + for tag, addr, path in fdlList: + if tag == 'single': + fdlSingleName = path + fdlSingleAddr = auto_int(addr) + elif tag == 'fdl1': + fdl1Name = path + fdl1Addr = auto_int(addr) + elif tag == 'fdl2': + fdl2Name = path + fdl2Addr = auto_int(addr) + + # override target with the individual parameters if necessary UNISOC_VID = args.device_vid UNISOC_PID = args.device_pid - fdl1Addr = args.fdl1_addr - fdl2Addr = args.fdl2_addr - fdl1Name = args.fdl1_file - fdl2Name = args.fdl2_file - fdlSingleName = args.single_fdl_file - fdlSingleAddr = args.single_fdl_addr + if args.fdl1_addr is not None: + fdl1Addr = args.fdl1_addr + if args.fdl2_addr is not None: + fdl2Addr = args.fdl2_addr + if args.fdl1_file is not None: + fdl1Name = args.fdl1_file + if args.fdl2_file is not None: + fdl2Name = args.fdl2_file + if args.single_fdl_file is not None: + fdlSingleName = args.single_fdl_file + if args.single_fdl_addr is not None: + fdlSingleAddr = args.single_fdl_addr outfile = args.file partitionId = args.partid readbs = args.block_size readoffset = args.start readlen = args.length forceErase = args.force_erase + sendEnableWriteFlash = args.enable_write_flash singleFdlMode = False fdl1Label = 'FDL1' fdl2Label = 'FDL2' + if args.flash_noremap == True: + print('Flash remapping disabled') + UNISOC_FLASH_BASE_ADDR = 0 + if fdlSingleName is not None: singleFdlMode = True fdl1Addr = fdlSingleAddr @@ -250,6 +292,12 @@ if __name__ == '__main__': # main app start print(fdl2Label + ' running, may start interacting with flash memory') if is_flash: + + if sendEnableWriteFlash: + resp = reqresp(unicmd.cmd_enable_write_flash(), True) + rcode, rlen, r = unicmd.resp_decode(resp, True) + assert rcode == unicmd.BSL_REP_ACK, 'Could not send the flash write request, response code is %X' % rcode + print('Writing flash at offset 0x%X from %s...' % (readoffset, outfile)) write_flash_mem(outfile, readoffset, readbs, forceErase) print('Flash memory written, disconnect the device!')