xous-core/tools/restore.py

765 lines
32 KiB
Python

#! /usr/bin/env python3
import argparse
import usb.core
import usb.util
import array
import sys
import hashlib
import csv
import urllib.request
from datetime import datetime
import requests
from Crypto.Hash import SHA512
from progressbar.bar import ProgressBar
class PrecursorUsb:
def __init__(self, dev):
self.dev = dev
self.RDSR = 0x05
self.RDSCUR = 0x2B
self.RDID = 0x9F
self.WREN = 0x06
self.WRDI = 0x04
self.SE4B = 0x21
self.BE4B = 0xDC
self.PP4B = 0x12
self.registers = {}
self.regions = {}
self.gitrev = ''
def register(self, name):
return int(self.registers[name], 0)
def halt(self):
if 'vexriscv_debug' in self.regions:
self.poke(int(self.regions['vexriscv_debug'][0], 0), 0x00020000)
elif 'reboot_cpu_hold_reset' in self.registers:
self.poke(self.register('reboot_cpu_hold_reset'), 1)
else:
print("Can't find reset CSR. Try updating to the latest version of this program")
def unhalt(self):
if 'vexriscv_debug' in self.regions:
self.poke(int(self.regions['vexriscv_debug'][0], 0), 0x02000000)
elif 'reboot_cpu_hold_reset' in self.registers:
self.poke(self.register('reboot_cpu_hold_reset'), 0)
else:
print("Can't find reset CSR. Try updating to the latest version of this program")
def peek(self, addr, display=False):
_dummy_s = '\x00'.encode('utf-8')
data = array.array('B', _dummy_s * 4)
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
data_or_wLength=data, timeout=500)
read_data = int.from_bytes(data.tobytes(), byteorder='little', signed=False)
if display == True:
print("0x{:08x}".format(read_data))
return read_data
def poke(self, addr, wdata, check=False, display=False):
if check == True:
_dummy_s = '\x00'.encode('utf-8')
data = array.array('B', _dummy_s * 4)
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
data_or_wLength=data, timeout=500)
read_data = int.from_bytes(data.tobytes(), byteorder='little', signed=False)
print("before poke: 0x{:08x}".format(read_data))
data = array.array('B', wdata.to_bytes(4, 'little'))
numwritten = self.dev.ctrl_transfer(bmRequestType=(0x00 | 0x43), bRequest=0,
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
data_or_wLength=data, timeout=500)
if check == True:
_dummy_s = '\x00'.encode('utf-8')
data = array.array('B', _dummy_s * 4)
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
wValue=(addr & 0xffff), wIndex=((addr >> 16) & 0xffff),
data_or_wLength=data, timeout=500)
read_data = int.from_bytes(data.tobytes(), byteorder='little', signed=False)
print("after poke: 0x{:08x}".format(read_data))
if display == True:
print("wrote 0x{:08x} to 0x{:08x}".format(wdata, addr))
def burst_read(self, addr, len):
_dummy_s = '\x00'.encode('utf-8')
maxlen = 4096
ret = bytearray()
packet_count = len // maxlen
if (len % maxlen) != 0:
packet_count += 1
for pkt_num in range(packet_count):
cur_addr = addr + pkt_num * maxlen
if pkt_num == packet_count - 1:
if len % maxlen != 0:
bufsize = len % maxlen
else:
bufsize = maxlen
else:
bufsize = maxlen
data = array.array('B', _dummy_s * bufsize)
numread = self.dev.ctrl_transfer(bmRequestType=(0x80 | 0x43), bRequest=0,
wValue=(cur_addr & 0xffff), wIndex=((cur_addr >> 16) & 0xffff),
data_or_wLength=data, timeout=500)
if numread != bufsize:
print("Burst read error: {} bytes requested, {} bytes read at 0x{:08x}".format(bufsize, numread, cur_addr))
exit(1)
ret = ret + data
return ret
def burst_write(self, addr, data):
if len(data) == 0:
return
# the actual "addr" doesn't matter for a burst_write, because it's specified
# as an argument to the flash_pp4b command. We lock out access to the base of
# SPINOR because it's part of the gateware, so, we pick a "safe" address to
# write to instead. The page write responder will aggregate any write data
# to anywhere in the SPINOR address range.
writebuf_addr = 0x2098_0000 # the current start address of the kernel, for example
maxlen = 4096
packet_count = len(data) // maxlen
if (len(data) % maxlen) != 0:
packet_count += 1
for pkt_num in range(packet_count):
cur_addr = addr + pkt_num * maxlen
if pkt_num == packet_count - 1:
if len(data) % maxlen != 0:
bufsize = len(data) % maxlen
else:
bufsize = maxlen
else:
bufsize = maxlen
wdata = array.array('B', data[(pkt_num * maxlen):(pkt_num * maxlen) + bufsize])
numwritten = self.dev.ctrl_transfer(bmRequestType=(0x00 | 0x43), bRequest=0,
# note use of writebuf_addr instead of cur_addr -> see comment above about the quirk of write addressing
wValue=(writebuf_addr & 0xffff), wIndex=((writebuf_addr >> 16) & 0xffff),
data_or_wLength=wdata, timeout=500)
if numwritten != bufsize:
print("Burst write error: {} bytes requested, {} bytes written at 0x{:08x}".format(bufsize, numwritten, cur_addr))
exit(1)
def ping_wdt(self):
self.poke(self.register('wdt_watchdog'), 1, display=False)
self.poke(self.register('wdt_watchdog'), 1, display=False)
def spinor_command_value(self, exec=0, lock_reads=0, cmd_code=0, dummy_cycles=0, data_words=0, has_arg=0):
return ((exec & 1) << 1 |
(lock_reads & 1) << 24 |
(cmd_code & 0xff) << 2 |
(dummy_cycles & 0x1f) << 11 |
(data_words & 0xff) << 16 |
(has_arg & 1) << 10
)
def flash_rdsr(self, lock_reads):
self.poke(self.register('spinor_cmd_arg'), 0)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, lock_reads=lock_reads, cmd_code=self.RDSR, dummy_cycles=4, data_words=1, has_arg=1)
)
return self.peek(self.register('spinor_cmd_rbk_data'), display=False)
def flash_rdscur(self):
self.poke(self.register('spinor_cmd_arg'), 0)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.RDSCUR, dummy_cycles=4, data_words=1, has_arg=1)
)
return self.peek(self.register('spinor_cmd_rbk_data'), display=False)
def flash_rdid(self, offset):
self.poke(self.register('spinor_cmd_arg'), 0)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, cmd_code=self.RDID, dummy_cycles=4, data_words=offset, has_arg=1)
)
return self.peek(self.register('spinor_cmd_rbk_data'), display=False)
def flash_wren(self):
self.poke(self.register('spinor_cmd_arg'), 0)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.WREN)
)
def flash_wrdi(self):
self.poke(self.register('spinor_cmd_arg'), 0)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.WRDI)
)
def flash_se4b(self, sector_address):
self.poke(self.register('spinor_cmd_arg'), sector_address)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.SE4B, has_arg=1)
)
def flash_be4b(self, block_address):
self.poke(self.register('spinor_cmd_arg'), block_address)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.BE4B, has_arg=1)
)
def flash_pp4b(self, address, data_bytes):
self.poke(self.register('spinor_cmd_arg'), address)
self.poke(self.register('spinor_command'),
self.spinor_command_value(exec=1, lock_reads=1, cmd_code=self.PP4B, has_arg=1, data_words=(data_bytes//2))
)
def load_csrs(self, fname=None):
LOC_CSRCSV = 0x20277000 # this address shouldn't change because it's how we figure out our version number
# CSR extraction:
# dd if=soc_csr.bin of=csr_data_0.9.6.bin skip=2524 count=32 bs=1024
if fname == None:
csr_data = self.burst_read(LOC_CSRCSV, 0x8000)
else:
with open(fname, "rb") as f:
csr_data = f.read(0x8000)
hasher = hashlib.sha512()
hasher.update(csr_data[:0x7FC0])
digest = hasher.digest()
if digest != csr_data[0x7fc0:]:
print("Could not find a valid csr.csv descriptor on the device, aborting!")
exit(1)
csr_len = int.from_bytes(csr_data[:4], 'little')
csr_extracted = csr_data[4:4+csr_len]
decoded = csr_extracted.decode('utf-8')
# strip comments
stripped = []
for line in decoded.split('\n'):
if line.startswith('#') == False:
stripped.append(line)
# create database
csr_db = csv.reader(stripped)
for row in csr_db:
if len(row) > 1:
if 'csr_register' in row[0]:
self.registers[row[1]] = row[2]
if 'memory_region' in row[0]:
self.regions[row[1]] = [row[2], row[3]]
if 'git_rev' in row[0]:
self.gitrev = row[1]
print("Using SoC {} registers".format(self.gitrev))
def erase_region(self, addr, length):
# ID code check
code = self.flash_rdid(1)
print("ID code bytes 1-2: 0x{:08x}".format(code))
if code != 0x8080c2c2:
print("ID code mismatch")
exit(1)
code = self.flash_rdid(2)
print("ID code bytes 2-3: 0x{:08x}".format(code))
if code != 0x3b3b8080:
print("ID code mismatch")
exit(1)
# block erase
progress = ProgressBar(min_value=0, max_value=length, prefix='Erasing ').start()
erased = 0
while erased < length:
self.ping_wdt()
if (length - erased >= 65536) and ((addr & 0xFFFF) == 0):
blocksize = 65536
else:
blocksize = 4096
while True:
self.flash_wren()
status = self.flash_rdsr(1)
if status & 0x02 != 0:
break
if blocksize == 4096:
self.flash_se4b(addr + erased)
else:
self.flash_be4b(addr + erased)
erased += blocksize
while (self.flash_rdsr(1) & 0x01) != 0:
pass
result = self.flash_rdscur()
if result & 0x60 != 0:
print("E_FAIL/P_FAIL set on erase, programming may fail, but trying anyways...")
if self.flash_rdsr(1) & 0x02 != 0:
self.flash_wrdi()
while (self.flash_rdsr(1) & 0x02) != 0:
pass
if erased < length:
progress.update(erased)
progress.finish()
print("Erase finished")
# addr is relative to the base of FLASH (not absolute)
def flash_program(self, addr, data, verify=True):
flash_region = int(self.regions['spiflash'][0], 0)
flash_len = int(self.regions['spiflash'][1], 0)
if (addr + len(data) > flash_len):
print("Write data out of bounds! Aborting.")
exit(1)
# ID code check
code = self.flash_rdid(1)
print("ID code bytes 1-2: 0x{:08x}".format(code))
if code != 0x8080c2c2:
print("ID code mismatch")
exit(1)
code = self.flash_rdid(2)
print("ID code bytes 2-3: 0x{:08x}".format(code))
if code != 0x3b3b8080:
print("ID code mismatch")
exit(1)
# block erase
progress = ProgressBar(min_value=0, max_value=len(data), prefix='Erasing ').start()
erased = 0
while erased < len(data):
self.ping_wdt()
if (len(data) - erased >= 65536) and ((addr & 0xFFFF) == 0):
blocksize = 65536
else:
blocksize = 4096
while True:
self.flash_wren()
status = self.flash_rdsr(1)
if status & 0x02 != 0:
break
if blocksize == 4096:
self.flash_se4b(addr + erased)
else:
self.flash_be4b(addr + erased)
erased += blocksize
while (self.flash_rdsr(1) & 0x01) != 0:
pass
result = self.flash_rdscur()
if result & 0x60 != 0:
print("E_FAIL/P_FAIL set on erase, programming may fail, but trying anyways...")
if self.flash_rdsr(1) & 0x02 != 0:
self.flash_wrdi()
while (self.flash_rdsr(1) & 0x02) != 0:
pass
if erased < len(data):
progress.update(erased)
progress.finish()
print("Erase finished")
# program
# pad out to the nearest word length
if len(data) % 4 != 0:
data += bytearray([0xff] * (4 - (len(data) % 4)))
written = 0
progress = ProgressBar(min_value=0, max_value=len(data), prefix='Writing ').start()
while written < len(data):
self.ping_wdt()
if len(data) - written > 256:
chunklen = 256
else:
chunklen = len(data) - written
while True:
self.flash_wren()
status = self.flash_rdsr(1)
if status & 0x02 != 0:
break
self.burst_write(self.register('spinor_wdata'), data[written:(written+chunklen)])
self.flash_pp4b(addr + written, chunklen)
written += chunklen
if written < len(data):
progress.update(written)
progress.finish()
print("Write finished")
if self.flash_rdsr(1) & 0x02 != 0:
self.flash_wrdi()
while (self.flash_rdsr(1) & 0x02) != 0:
pass
# dummy reads to clear the "read lock" bit
self.flash_rdsr(0)
# verify
self.ping_wdt()
if verify:
print("Performing readback for verification...")
self.ping_wdt()
rbk_data = self.burst_read(addr + flash_region, len(data))
if rbk_data != data:
errs = 0
err_thresh = 64
for i in range(0, len(rbk_data)):
if rbk_data[i] != data[i]:
if errs < err_thresh:
print("Error at 0x{:x}: {:x}->{:x}".format(i, data[i], rbk_data[i]))
errs += 1
if errs == err_thresh:
print("Too many errors, stopping print...")
print("Errors were found in verification, programming failed")
print("Total byte errors: {}".format(errs))
exit(1)
else:
print("Verification passed.")
else:
print("Skipped verification at user request")
self.ping_wdt()
def auto_int(x):
return int(x, 0)
def bytes_to_semverstr(b):
maj = int.from_bytes(b[0:2], 'little')
min = int.from_bytes(b[2:4], 'little')
rev = int.from_bytes(b[4:6], 'little')
extra = int.from_bytes(b[6:8], 'little')
has_commit = int.from_bytes(b[12:16], 'little')
if has_commit != 0:
commit = int.from_bytes(b[8:12], 'little')
return "v{}.{}.{}-{}-g{:x}".format(maj, min, rev, extra, commit)
else:
return "v{}.{}.{}-{}".format(maj, min, rev, extra)
class SemVer:
def __init__(self, b):
self.maj = int.from_bytes(b[0:2], 'little')
self.min = int.from_bytes(b[2:4], 'little')
self.rev = int.from_bytes(b[4:6], 'little')
self.extra = int.from_bytes(b[6:8], 'little')
self.has_commit = int.from_bytes(b[12:16], 'little')
# note: very old kernel will return a version of 0.0.0
if self.has_commit != 0:
self.commit = int.from_bytes(b[8:12], 'little')
def ord(self): # returns a number that you can use to compare if versions are bigger or smaller than each other
return self.maj << 48 | self.min << 32 | self.rev << 16 | self.extra
def as_str(self):
if self.has_commit == 0:
return "v{}.{}.{}-{}".format(self.maj, self.min, self.rev, self.extra)
else:
return "v{}.{}.{}-{}-g{:x}".format(self.maj, self.min, self.rev, self.extra, self.commit)
def single_yes_or_no_question(question, default_no=False):
choices = ' [y/N]: ' if default_no else ' [Y/n]: '
default_answer = 'n' if default_no else 'y'
reply = str(input(question + choices)).lower().strip() or default_answer
if reply[0] == 'y':
return True
if reply[0] == 'n':
return False
else:
return False if default_no else True
def main():
parser = argparse.ArgumentParser(description="Update/upload to a Precursor device running Xous 0.8/0.9")
parser.add_argument(
"--config", required=False, help="Print the descriptor", action='store_true'
)
parser.add_argument(
"--override-csr", required=False, help="CSR file to use instead of CSR values stored with the image. Used to recover in case of partial update of soc_csr.bin", type=str,
)
parser.add_argument(
"--peek", required=False, help="Inspect an address", type=auto_int, metavar=('ADDR')
)
parser.add_argument(
"--file", required=False, help="File to restore from. Defaults to backup.pddb", default="backup.pddb", type=str
)
args = parser.parse_args()
dev = usb.core.find(idProduct=0x5bf0, idVendor=0x1209)
if dev is None:
raise ValueError('Precursor device not found')
dev.set_configuration()
if args.config:
cfg = dev.get_active_configuration()
print(cfg)
pc_usb = PrecursorUsb(dev)
if args.peek:
pc_usb.peek(args.peek, display=True)
# print(burst_read(dev, args.peek, 256).hex())
exit(0)
pc_usb.load_csrs(args.override_csr) # prime the CSR values
if "v0.8" in pc_usb.gitrev:
locs = {
"LOC_SOC" : [0x0000_0000, "soc_csr.bin"],
"LOC_STAGING": [0x0028_0000, "pass"],
"LOC_LOADER" : [0x0050_0000, "loader.bin"],
"LOC_KERNEL" : [0x0098_0000, "xous.img"],
"LOC_WF200" : [0x07F8_0000, "pass"],
"LOC_EC" : [0x07FC_E000, "pass"],
"LOC_AUDIO" : [0x0634_0000, "short_8khz.wav"],
"LEN_AUDIO" : [0x01C4_0000, "pass"],
"LOC_PDDB" : [0x0100_0000, "pass"],
}
elif "v0.9" in pc_usb.gitrev:
locs = {
"LOC_SOC" : [0x0000_0000, "soc_csr.bin"],
"LOC_STAGING": [0x0028_0000, "pass"],
"LOC_LOADER" : [0x0050_0000, "loader.bin"],
"LOC_KERNEL" : [0x0098_0000, "xous.img"],
"LOC_WF200" : [0x07F8_0000, "pass"],
"LOC_EC" : [0x07FC_E000, "pass"],
"LOC_AUDIO" : [0x0634_0000, "short_8khz.wav"],
"LEN_AUDIO" : [0x01C4_0000, "pass"],
"LOC_PDDB" : [0x01D8_0000, "pass"],
}
elif args.force == True:
# try the v0.9 offsets
locs = {
"LOC_SOC" : [0x00000000, "soc_csr.bin"],
"LOC_STAGING": [0x00280000, "pass"],
"LOC_LOADER" : [0x00500000, "loader.bin"],
"LOC_KERNEL" : [0x00980000, "xous.img"],
"LOC_WF200" : [0x07F80000, "pass"],
"LOC_EC" : [0x07FCE000, "pass"],
"LOC_AUDIO" : [0x06340000, "short_8khz.wav"],
"LEN_AUDIO" : [0x01C40000, "pass"],
"LOC_PDDB" : [0x01D80000, "pass"],
}
else:
print("SoC is from an unknow rev '{}', use --force to continue anyways with v0.9 firmware offsets".format(pc_usb.load_csrs()))
exit(1)
LANGUAGE = {
0: "en",
1: "en-tts",
2: "ja",
3: "zh",
}
try:
with open(args.file, "rb") as backup_file:
backup = bytearray(backup_file.read())
i = 0
backup_version = int.from_bytes(backup[i:i+4], 'little')
print("Backup protocol version: 0x{:08x}".format(backup_version))
i += 4
print("Xous version: {}".format(bytes_to_semverstr(backup[i:i+16])))
backup_xous_ver = SemVer(backup[i:i+16])
i += 16
print("SOC version: {}".format(bytes_to_semverstr(backup[i:i+16])))
i += 16
print("EC version: {}".format(bytes_to_semverstr(backup[i:i+16])))
i += 16
print("WF200 version: {}".format(bytes_to_semverstr(backup[i:i+16])))
i += 16
i += 4 # padding because align=8
ts = int.from_bytes(backup[i:i+8], 'little') / 1000
print("Timestamp: {} / {}".format(ts, datetime.utcfromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')))
i += 8
lcode = int.from_bytes(backup[i:i+4], 'little')
print("Language code: {}".format(lcode))
if lcode < len(LANGUAGE):
language = LANGUAGE[lcode]
else:
language = "en"
i += 4
print("Keyboard layout code: {}".format(int.from_bytes(backup[i:i+4], 'little')))
i += 4
print("DNA: 0x{:x}".format(int.from_bytes(backup[i:i+8], 'little')))
i += 8
checksum_region_len = int.from_bytes(backup[i:i+4], 'little') * 4096
print("Checksum region length: 0x{:x}".format(checksum_region_len))
i += 4
total_checksums = int.from_bytes(backup[i:i+4], 'little')
print("Number of checksum blocks: {}".format(total_checksums)) # if this is 0, checksumming was skipped
i += 4
header_total_size = int.from_bytes(backup[i:i+4], 'little')
print("Header total length in bytes: {}".format(header_total_size))
i += 4
i += 36 # reserved
if backup_version == 0x10001:
checksum_errors = False
check_region = backup[:0x5d0]
checksum = backup[0x5d0:0x5f0]
print("Doing hash verification of pt+ct metadata...")
hasher = SHA512.new(truncate="256")
hasher.update(check_region)
computed_checksum = hasher.digest()
if computed_checksum != checksum:
print("Header failed hash integrity check!")
print("Calculated: {}".format(computed_checksum.hex()))
print("Expected: {}".format(checksum.hex()))
exit(1)
else:
print("Header passed integrity check.")
if total_checksums != 0:
raw_checksums = backup[header_total_size-(total_checksums * 16):header_total_size]
checksums = [raw_checksums[i:i+16] for i in range(0, len(raw_checksums), 16)]
check_block_num = 0
while check_block_num < total_checksums:
hasher = SHA512.new(truncate="256")
hasher.update(
backup[
header_total_size + check_block_num * checksum_region_len:
header_total_size + (check_block_num + 1) * checksum_region_len
])
sum = hasher.digest()
if sum[:16] != checksums[check_block_num]:
print("Bad checksum on block {} at offset 0x{:x}".format(check_block_num, check_block_num * checksum_region_len))
print(" Calculated: {}".format(sum[:16].hex()))
print(" Expected: {}".format(checksums[check_block_num].hex()))
checksum_errors = True
check_block_num += 1
if checksum_errors:
print("Media errors were detected! Backup may be unusable, aborting restore.")
exit(1)
else:
print("No media errors detected, {} blocks passed checksum tests".format(total_checksums))
if i != 0x90:
# this is a sanity check for myself, and python doesn't like it when i make this an assert
# because it does the math and realizes it's always true but the point is I don't want to do the math.
print("Plaintext operand offset calculated incorrectly! Check data structure sizes.")
exit(1)
backup[i:i+4] = (2).to_bytes(4, 'little')
op = int.from_bytes(backup[i:i+4], 'little')
print("Opcode (should be 2, to trigger the next phase of restore): {}".format(op))
assert(op==2)
# now try to download all the artifacts and check their versions
# this list should visit kernels in order from newest to oldest.
URL_LIST = [
'https://ci.betrusted.io/releases/v0.9.15/',
'https://ci.betrusted.io/releases/v0.9.14/',
'https://ci.betrusted.io/releases/v0.9.13/',
'https://ci.betrusted.io/releases/v0.9.12/',
'https://ci.betrusted.io/releases/v0.9.11/',
'https://ci.betrusted.io/releases/v0.9.10/',
'https://ci.betrusted.io/releases/v0.9.9/',
'https://ci.betrusted.io/releases/v0.9.8/',
'https://ci.betrusted.io/releases/v0.9.7/'
]
if False: # insert bleeding-edge build for pre-release testing
URL_LIST.insert(0, 'https://ci.betrusted.io/latest-ci/')
attempt = 0
while attempt < len(URL_LIST):
# first try the stable branch and see if it meets the version requirement
print("Downloading candidate restore kernel...")
candidate_kernel = requests.get(URL_LIST[attempt] + 'xous-' + language + '.img').content
if int.from_bytes(candidate_kernel[:4], 'little') != 1 and int.from_bytes(candidate_kernel[:4], 'little') != 2:
print("Downloaded kernel image has unexpected signature version. Trying the next image.")
attempt += 1
continue
kern_len = int.from_bytes(candidate_kernel[4:8], 'little') + 0x1000
if len(candidate_kernel) != kern_len:
print("Downloaded kernel has the wrong length. Trying the next image.")
attempt += 1
continue
minver_loc = kern_len - 4 - 4 - 16 - 16 # length, sigver, current semver, then minver
curver_loc = kern_len - 4 - 4 - 16
min_compat_kernel = SemVer(candidate_kernel[minver_loc:minver_loc + 16])
if min_compat_kernel.ord() > backup_xous_ver.ord():
print("Downloaded kernel is too new for the backup, trying an older version...")
attempt += 1
continue
# alright, we should now be at a base URL where everything is a match. We skip checking
# the rest of the versions because it "should" be an ensemble.
curver = SemVer(candidate_kernel[curver_loc:curver_loc + 16])
# print("Min ver: {}".format(min_compat_kernel.as_str()))
# print("Cur ver: {}".format(curver.as_str()))
print("Found viable kernel!")
kernel = candidate_kernel
print("Downloading loader...")
loader = requests.get(URL_LIST[attempt] + 'loader.bin').content
print("Downloading gateware...")
soc_csr = requests.get(URL_LIST[attempt] + 'soc_csr.bin').content
print("Downloading embedded controller...")
ec_fw = requests.get(URL_LIST[attempt] + 'ec_fw.bin').content
print("Downloading wf200...")
wf200 = requests.get(URL_LIST[attempt] + 'wf200_fw.bin').content
break
print("The restore process takes about 30 minutes on a computer with an optimal USB stack (most Intel PC & Rpi), and about 2.5 hours on one with a buggy USB stack (most Macs & AMD PC).")
if False == single_yes_or_no_question("Proceed with copying restore data from Xous release {}? ".format(curver.as_str())):
print("Abort by user request.")
exit(0)
pc_usb.ping_wdt()
print("Halting CPU.")
pc_usb.halt()
worklist = [
['erase', "Disabling boot by erasing loader...", locs['LOC_LOADER'][0], 1024 * 256],
['prog', "Uploading kernel", locs['LOC_KERNEL'][0], kernel],
['prog', "Uploading EC", locs['LOC_EC'][0], ec_fw],
['prog', "Uploading wf200", locs['LOC_WF200'][0], wf200],
['prog', "Uploading gateware", locs['LOC_STAGING'][0], soc_csr],
['prog', "Uploading PDDB", locs['LOC_PDDB'][0] - 0x1000, backup], # the PDDB file has a 4096-byte prefix
['prog', "Restoring loader", locs['LOC_LOADER'][0], loader],
]
for work in worklist:
success = False
while success == False:
try:
print(work[1])
if work[0] == 'erase':
#print("pretend erase {}".format(work[2]))
pc_usb.erase_region(work[2], work[3])
success = True
else:
#print("prentend upload: {}".format(work[2]))
pc_usb.flash_program(work[2], work[3], verify=False)
success = True
except:
print("Error encountered while {}".format(work[1]))
print("Try reseating the USB connection.")
if False == single_yes_or_no_question("Try again? "):
print("Abort by user request. System may not be bootable, but you can try again later.")
exit(0)
print("Restore finished copying objects.\nYou will need to reboot by inserting a paperclip in the hole in the lower right hand side,\nand follow on-screen instructions")
print("Resuming CPU.")
pc_usb.unhalt()
print("Resetting SOC...")
try:
pc_usb.poke(pc_usb.register('reboot_soc_reset'), 0xac, display=False)
except usb.core.USBError:
pass # we expect an error because we reset the SOC and that includes the USB core
except:
print("`backup.pddb` could not be opened. Aborting.")
exit(1)
if __name__ == "__main__":
main()
exit(0)