312 lines
12 KiB
Python
312 lines
12 KiB
Python
#!/usr/bin/env python
|
|
|
|
import usb
|
|
import sys, time
|
|
import os
|
|
import unicmd
|
|
|
|
# global params
|
|
|
|
UNISOC_VID = 0x1782
|
|
UNISOC_PID = 0x4d00
|
|
UNISOC_FLASH_BASE_ADDR = 0x10000000
|
|
MAX_PKT_SIZE = 1024
|
|
bSize = 512 # read block size
|
|
genTimeout = 120000
|
|
|
|
# all main procedures
|
|
|
|
def connect(vid, pid):
|
|
while True:
|
|
dev = usb.core.find(idVendor=vid, idProduct=pid)
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
if dev is not None:
|
|
print('\nDevice connected')
|
|
break
|
|
time.sleep(0.1)
|
|
dev.set_configuration()
|
|
cfg = dev.get_active_configuration()
|
|
intf = cfg[(0,0)]
|
|
epIn = usb.util.find_descriptor(
|
|
intf,
|
|
custom_match = \
|
|
lambda e: \
|
|
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
|
usb.util.ENDPOINT_IN)
|
|
epOut = usb.util.find_descriptor(
|
|
intf,
|
|
custom_match = \
|
|
lambda e: \
|
|
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
|
usb.util.ENDPOINT_OUT)
|
|
assert epIn is not None
|
|
assert epOut is not None
|
|
return dev, epIn, epOut
|
|
|
|
|
|
def reqonly(packet, fdlBooted = False, noCrc = False):
|
|
packet = unicmd.hdlc_encode(packet, fdlBooted, noCrc)
|
|
dev.write(epOut, packet, genTimeout)
|
|
|
|
def reqresp(packet, fdlBooted = False, noCrc = False):
|
|
reqonly(packet, fdlBooted, noCrc)
|
|
resp = bytes(dev.read(epIn, bSize, genTimeout))
|
|
return resp
|
|
|
|
def handshake(fdlBooted = False):
|
|
resp = reqresp(unicmd.cmd_sync(), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
if len(r):
|
|
print('>', r.decode())
|
|
resp = reqresp(unicmd.cmd_connect(), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
if len(r):
|
|
print('>', r.decode())
|
|
|
|
def send_file_to_addr(fname, faddr, fdlBooted = False, flashMode = False, fbs = 1024):
|
|
pSize = MAX_PKT_SIZE
|
|
print('Initializing data transfer...')
|
|
dataCrc = 0
|
|
if flashMode:
|
|
# faddr is our flash offset in this case, and we directly pass the data buffer to the function
|
|
pSize = fbs
|
|
fdata = fname
|
|
flen = len(fdata)
|
|
else:
|
|
f = open(fname, 'rb')
|
|
fdata = f.read()
|
|
f.close()
|
|
flen = len(fdata)
|
|
dataCrc = unicmd.chksum32(fdata)
|
|
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]
|
|
resp = reqresp(unicmd.cmd_data_send(buf), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Something is wrong and response code is %X, block is %s' % (rcode, buf.hex())
|
|
fdata = fdata[pSize:]
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
print('\nEnding data transfer...')
|
|
resp = reqresp(unicmd.cmd_data_end(), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
if not flashMode or (rcode != unicmd.BSL_FLASH_CFG_ERROR and rcode != unicmd.BSL_WRITE_ERROR): # on flashing, ignore 0xA2 and 0xA4 errors
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not finalize data transfer, response code is %X' % rcode
|
|
print('Data transfer successful')
|
|
|
|
# readback code implementation
|
|
|
|
def read_partdata(partid, size, offset):
|
|
t = b''
|
|
reqonly(unicmd.cmd_read_flash(partid, size, offset), True)
|
|
while True:
|
|
xr = bytes(dev.read(epIn, bSize, genTimeout))
|
|
t += xr
|
|
if len(xr) < bSize:
|
|
break
|
|
return t
|
|
|
|
def read_partition(partid, partsize, partoffset, outfile, rbblocksize):
|
|
outf = open(outfile, 'wb')
|
|
psize = partsize
|
|
offset = partoffset
|
|
print('Dumping %d bytes from partition 0x%X at offset 0x%X to %s...' % (partsize, partid, partoffset, outfile))
|
|
bufsize = rbblocksize
|
|
while psize > 0:
|
|
if psize < bufsize:
|
|
bufsize = psize
|
|
resp = read_partdata(partid, bufsize, offset)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
outf.write(r)
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
psize -= rlen
|
|
offset += rlen
|
|
outf.flush()
|
|
print('\nPartition dumped!')
|
|
|
|
# memory eraser and writer
|
|
|
|
def erase_flash_mem(size, faddr):
|
|
print('Erasing %d bytes in the flash memory at offset 0x%X...' % (size, faddr - UNISOC_FLASH_BASE_ADDR))
|
|
resp = reqresp(unicmd.cmd_erase_flash(faddr, size), True)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not erase flash memory, response code is %X' % rcode
|
|
print('Flash range erased!')
|
|
|
|
def write_flash_mem(infile, offset, blocksize, forceErase):
|
|
startAddr = UNISOC_FLASH_BASE_ADDR + offset
|
|
f = open(infile, 'rb')
|
|
fdata = f.read()
|
|
f.close()
|
|
flen = len(fdata)
|
|
if 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)
|
|
|
|
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/Spreadtrum feature phone flash reader/writer', epilog='(c) Luxferre 2021 --- No rights reserved <https://unlicense.org>')
|
|
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('-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=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()
|
|
|
|
is_flash = False
|
|
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
|
|
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
|
|
fdl1Name = fdlSingleName
|
|
fdl1Label = 'FDL'
|
|
fdl2Label = 'FDL'
|
|
print('Using a single FDL %s, loading to 0x%X' % (fdlSingleName, fdlSingleAddr))
|
|
else:
|
|
print('Using FDL1 %s, loading to 0x%X' % (fdl1Name, fdl1Addr))
|
|
print('Using FDL2 %s, loading to 0x%X' % (fdl2Name, fdl2Addr))
|
|
|
|
# initial connection
|
|
print('Connect the device %X:%X while holding the bootkey...' % (UNISOC_VID, UNISOC_PID) )
|
|
dev, epIn, epOut = connect(UNISOC_VID, UNISOC_PID)
|
|
handshake()
|
|
|
|
def reconnect():
|
|
global dev
|
|
if dev is not None:
|
|
usb.util.dispose_resources(dev)
|
|
time.sleep(0.5)
|
|
dev, epIn, epOut = connect(UNISOC_VID, UNISOC_PID)
|
|
|
|
print('Boot mode entered')
|
|
|
|
print('Sending ' + fdl1Label)
|
|
send_file_to_addr(fdl1Name, fdl1Addr)
|
|
print('Starting ' + fdl1Label)
|
|
resp = reqresp(unicmd.cmd_data_exec(fdl1Addr))
|
|
rcode, rlen, r = unicmd.resp_decode(resp, False)
|
|
if rcode == unicmd.BSL_REP_ACK:
|
|
print(fdl1Label + ' started successfully, reconnecting...')
|
|
reconnect()
|
|
handshake(True)
|
|
|
|
if singleFdlMode:
|
|
rcode = unicmd.BSL_REP_ACK
|
|
else:
|
|
print('Protocol set up, sending FDL2')
|
|
send_file_to_addr(fdl2Name, fdl2Addr, True)
|
|
print('Starting FDL2')
|
|
resp = reqresp(unicmd.cmd_data_exec(fdl2Addr), True)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
if rcode == unicmd.BSL_REP_ACK:
|
|
print(fdl2Label + ' started successfully!')
|
|
|
|
resp = reqresp(unicmd.cmd_sync_full(), True)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not set the baudrate, response code is %X' % rcode
|
|
|
|
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!')
|
|
else:
|
|
read_partition(partitionId, readlen, readoffset, outfile, readbs)
|
|
resp = reqresp(unicmd.cmd_reset(), True)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not reset the device, response code is %X' % rcode
|
|
|
|
if dev is not None:
|
|
usb.util.dispose_resources(dev)
|