222 lines
6.7 KiB
Python
222 lines
6.7 KiB
Python
# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
|
#
|
|
# vdisk.py - Share a virtual RAM disk with a USB host.
|
|
#
|
|
#
|
|
import os, sys, pyb, ckcc, version, glob, uasyncio, utime
|
|
from sigheader import FW_MIN_LENGTH
|
|
from version import MAX_UPLOAD_LEN, is_devmode
|
|
from usb import enable_usb, disable_usb
|
|
from uasyncio import sleep_ms
|
|
|
|
MIN_QUIET_TIME = 250 # (ms) delay after host writes disk, before we look at it.
|
|
|
|
def _host_done_cb(_psram):
|
|
# get back into the singleton
|
|
assert glob.VD
|
|
if glob.VD:
|
|
glob.VD.host_done_handler()
|
|
|
|
# singleton: block device implemented on half of the PSRAM
|
|
VBLKDEV = ckcc.PSRAM()
|
|
|
|
class VirtDisk:
|
|
def __init__(self):
|
|
# Feature is enabled, altho USB might be off.
|
|
glob.VD = self
|
|
|
|
self.ignore = set()
|
|
self.contents = self.sample()
|
|
|
|
assert ckcc.PSRAM
|
|
VBLKDEV.callback(_host_done_cb)
|
|
VBLKDEV.set_inserted(True)
|
|
|
|
def shutdown(self):
|
|
# we've been disabled, stop
|
|
VBLKDEV.set_inserted(False)
|
|
VBLKDEV.callback(None)
|
|
glob.VD = None
|
|
|
|
def unmount(self, written_files, readonly=False):
|
|
# just unmount; ignore errors
|
|
try:
|
|
os.umount('/vdisk')
|
|
except:
|
|
pass
|
|
|
|
# ignore the files we write ourselves
|
|
for fn in written_files:
|
|
if fn.startswith('/vdisk/'):
|
|
self.ignore.add(fn)
|
|
|
|
# allow host to change again
|
|
if not readonly:
|
|
enable_usb()
|
|
if glob.VD:
|
|
VBLKDEV.set_inserted(True)
|
|
|
|
def mount(self, readonly=False):
|
|
# Prepare to read the filesystem. Block host. Return mount pt.
|
|
for _ in range(10):
|
|
# wait until it's been idle for a little bit
|
|
host = VBLKDEV.get_time()
|
|
if utime.ticks_diff(utime.ticks_ms(), host) > MIN_QUIET_TIME:
|
|
break
|
|
utime.sleep_ms(MIN_QUIET_TIME//5)
|
|
else:
|
|
print("busy disk?")
|
|
|
|
try:
|
|
if not readonly:
|
|
disable_usb()
|
|
VBLKDEV.set_inserted(False)
|
|
os.mount(VBLKDEV, '/vdisk', readonly=readonly)
|
|
st = os.statvfs('/vdisk')
|
|
|
|
return '/vdisk'
|
|
except OSError as exc:
|
|
# corrupt or unformated?
|
|
# XXX incomplete error handling here; needs work
|
|
VBLKDEV.set_inserted(True)
|
|
sys.print_exception(exc)
|
|
|
|
return None
|
|
|
|
def sample(self):
|
|
# Peek at the contents of the disk right now
|
|
# - only root directory
|
|
# - only files, and capture their sizes
|
|
try:
|
|
os.mount(VBLKDEV, '/vdisk', readonly=True)
|
|
|
|
return list(sorted(('/vdisk/'+fn, sz) for (fn,ty,_,sz) in os.ilistdir('/vdisk')
|
|
if ty == 0x8000))
|
|
except BaseException as exc:
|
|
sys.print_exception(exc)
|
|
|
|
return []
|
|
finally:
|
|
os.umount('/vdisk')
|
|
|
|
def import_file(self, filename, sz):
|
|
# copy file into another area of PSRAM where rest of system can use it
|
|
assert sz <= MAX_UPLOAD_LEN # too big
|
|
|
|
# I could not resist doing this in C... since we already have the
|
|
# data in memory, why mess around with file concepts?
|
|
actual = VBLKDEV.copy_file(0, filename.split('/')[-1])
|
|
|
|
assert actual == sz
|
|
|
|
return actual
|
|
|
|
def new_psbt(self, filename):
|
|
# New incoming PSBT has been detected, start to sign it.
|
|
from auth import sign_psbt_file
|
|
uasyncio.create_task(sign_psbt_file(filename, force_vdisk=True, ux_abort=True))
|
|
|
|
def new_firmware(self, filename, sz):
|
|
# potential new firmware file detected
|
|
# - copy to start of PSRAM, begin upgrade confirm
|
|
self.import_file(filename, sz)
|
|
uasyncio.create_task(psram_upgrade(filename, sz))
|
|
|
|
def host_done_handler(self):
|
|
from glob import settings
|
|
|
|
if settings.get('vidsk', 0) != 2:
|
|
# auto mode not enabled, so ignore changes
|
|
return
|
|
|
|
now = self.sample()
|
|
if now == self.contents:
|
|
# no-op change, common, ignore
|
|
# - timestamp changes, hidden files, MacOS BS, etc.
|
|
return
|
|
|
|
# clear ignored items once they are deleted
|
|
self.ignore.intersection_update(fn for fn,_ in now)
|
|
|
|
self.contents = now
|
|
|
|
# Look for files we want to taste; assume they have
|
|
# been fully written-out because we are called after a
|
|
# fairly long timeout
|
|
for fn, sz in now:
|
|
|
|
if fn in self.ignore:
|
|
continue
|
|
|
|
if fn[0] == '.' or not sz:
|
|
continue
|
|
|
|
if sz > MAX_UPLOAD_LEN: # == MAX_UPLOAD_LEN_MK4, see version.py
|
|
#print("%s: too big" % fn)
|
|
continue
|
|
|
|
lfn = fn.lower()
|
|
|
|
if lfn.endswith('.psbt') and sz > 100 and ("-signed" not in lfn):
|
|
self.ignore.add(fn)
|
|
self.new_psbt(fn)
|
|
break
|
|
|
|
if lfn.endswith('.dfu') and sz > FW_MIN_LENGTH:
|
|
self.ignore.add(fn) # in case they decline it
|
|
self.new_firmware(fn, sz)
|
|
break
|
|
|
|
async def wipe_disk(self):
|
|
# Reformat. Near instant.
|
|
from glob import dis
|
|
from mk4 import make_psram_fs
|
|
|
|
dis.fullscreen('Formatting...')
|
|
dis.progress_bar_show(0.1)
|
|
|
|
disable_usb()
|
|
VBLKDEV.wipe()
|
|
make_psram_fs()
|
|
enable_usb()
|
|
|
|
await sleep_ms(50)
|
|
dis.progress_bar_show(1)
|
|
await sleep_ms(250)
|
|
|
|
|
|
async def psram_upgrade(filename, size):
|
|
# Upgrade to firmware image already in PSRAM at offset zero.
|
|
from glob import dis, PSRAM
|
|
from files import dfu_parse
|
|
from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE
|
|
from ux import ux_show_story, the_ux
|
|
from sffile import SFFile
|
|
|
|
if PSRAM.read_at(0, 3) == b'Dfu':
|
|
with SFFile(0, size) as fp:
|
|
offset, size = dfu_parse(fp)
|
|
else:
|
|
# handle raw binary file
|
|
offset = 0
|
|
|
|
# pull out firmware header
|
|
hdr = PSRAM.read_at(offset+FW_HEADER_OFFSET, FW_HEADER_SIZE)
|
|
|
|
if filename == '/vdisk/dev.dfu' and is_devmode:
|
|
# skip the checking and display for us devs and "just do it"
|
|
# - the bootrom still does the checks, you just can't see useful errors
|
|
from pincodes import pa
|
|
assert pa.is_successful()
|
|
print("dev.dfu being installed")
|
|
dis.bootrom_takeover()
|
|
pa.firmware_upgrade(offset, size)
|
|
return
|
|
|
|
# get user buy-in and approval of the change.
|
|
from auth import authorize_upgrade
|
|
authorize_upgrade(hdr, size, hdr_check=True, psram_offset=offset)
|
|
|
|
|
|
# EOF
|