Skip to content

Commit 0c98c60

Browse files
committed
tools/mpremote: Add romfs query, build and deploy commands.
These commands use the `vfs.rom_ioctl()` function to manage the ROM partitions on a device, and create and deploy ROMFS images. Signed-off-by: Damien George <damien@micropython.org>
1 parent 840b641 commit 0c98c60

File tree

4 files changed

+385
-1
lines changed

4 files changed

+385
-1
lines changed

docs/reference/mpremote.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ The full list of supported commands are:
7878
- `mip <mpremote_command_mip>`
7979
- `mount <mpremote_command_mount>`
8080
- `unmount <mpremote_command_unmount>`
81+
- `romfs <mpremote_command_romfs>`
8182
- `rtc <mpremote_command_rtc>`
8283
- `sleep <mpremote_command_sleep>`
8384
- `reset <mpremote_command_reset>`
@@ -347,6 +348,29 @@ The full list of supported commands are:
347348
This happens automatically when ``mpremote`` terminates, but it can be used
348349
in a sequence to unmount an earlier mount before subsequent command are run.
349350

351+
.. _mpremote_command_romfs:
352+
353+
- **romfs** -- manage ROMFS partitions on the device:
354+
355+
.. code-block:: bash
356+
357+
$ mpremote romfs <sub-command>
358+
359+
``<sub-command>`` may be:
360+
361+
- ``romfs query`` to list all the available ROMFS partitions and their size
362+
- ``romfs [-o <output>] build <source>`` to create a ROMFS image from the given
363+
source directory; the default output file is the source appended by ``.romfs``
364+
- ``romfs [-p <partition>] deploy <source>`` to deploy a ROMFS image to the device;
365+
will also create a temporary ROMFS image if the source is a directory
366+
367+
The ``build`` and ``deploy`` sub-commands both support the ``-m``/``--mpy`` option
368+
to automatically compile ``.py`` files to ``.mpy`` when creating the ROMFS image.
369+
This option is enabled by default, but only works if the ``mpy_cross`` Python
370+
package has been installed (eg via ``pip install mpy_cross``). If the package is
371+
not installed then a warning is printed and ``.py`` files remain as is. Compiling
372+
of ``.py`` files can be disabled with the ``--no-mpy`` option.
373+
350374
.. _mpremote_command_rtc:
351375

352376
- **rtc** -- set/get the device clock (RTC):

tools/mpremote/mpremote/commands.py

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import binascii
12
import hashlib
23
import os
34
import sys
45
import tempfile
6+
import zlib
57

68
import serial.tools.list_ports
79

8-
from .transport import TransportError, stdout_write_bytes
10+
from .transport import TransportError, TransportExecError, stdout_write_bytes
911
from .transport_serial import SerialTransport
12+
from .romfs import make_romfs
1013

1114

1215
class CommandError(Exception):
@@ -478,3 +481,181 @@ def do_rtc(state, args):
478481
state.transport.exec("machine.RTC().datetime({})".format(timetuple))
479482
else:
480483
print(state.transport.eval("machine.RTC().datetime()"))
484+
485+
486+
def _do_romfs_query(state, args):
487+
state.ensure_raw_repl()
488+
state.did_action()
489+
490+
# Detect the romfs and get its associated device.
491+
state.transport.exec("import vfs")
492+
if not state.transport.eval("hasattr(vfs,'rom_ioctl')"):
493+
print("ROMFS is not enabled on this device")
494+
return
495+
num_rom_partitions = state.transport.eval("vfs.rom_ioctl(1)")
496+
if num_rom_partitions <= 0:
497+
print("No ROMFS partitions available")
498+
return
499+
500+
for rom_id in range(num_rom_partitions):
501+
state.transport.exec(f"dev=vfs.rom_ioctl(2,{rom_id})")
502+
has_object = state.transport.eval("hasattr(dev,'ioctl')")
503+
if has_object:
504+
rom_block_count = state.transport.eval("dev.ioctl(4,0)")
505+
rom_block_size = state.transport.eval("dev.ioctl(5,0)")
506+
rom_size = rom_block_count * rom_block_size
507+
print(
508+
f"ROMFS{rom_id} partition has size {rom_size} bytes ({rom_block_count} blocks of {rom_block_size} bytes each)"
509+
)
510+
else:
511+
rom_size = state.transport.eval("len(dev)")
512+
print(f"ROMFS{rom_id} partition has size {rom_size} bytes")
513+
romfs = state.transport.eval("bytes(memoryview(dev)[:12])")
514+
print(f" Raw contents: {romfs.hex(':')} ...")
515+
if not romfs.startswith(b"\xd2\xcd\x31"):
516+
print(" Not a valid ROMFS")
517+
else:
518+
size = 0
519+
for value in romfs[3:]:
520+
size = (size << 7) | (value & 0x7F)
521+
if not value & 0x80:
522+
break
523+
print(f" ROMFS image size: {size}")
524+
525+
526+
def _do_romfs_build(state, args):
527+
state.did_action()
528+
529+
if args.path is None:
530+
raise CommandError("romfs build: source path not given")
531+
532+
input_directory = args.path
533+
534+
if args.output is None:
535+
output_file = input_directory + ".romfs"
536+
else:
537+
output_file = args.output
538+
539+
romfs = make_romfs(input_directory, mpy_cross=args.mpy)
540+
541+
print(f"Writing {len(romfs)} bytes to output file {output_file}")
542+
with open(output_file, "wb") as f:
543+
f.write(romfs)
544+
545+
546+
def _do_romfs_deploy(state, args):
547+
state.ensure_raw_repl()
548+
state.did_action()
549+
transport = state.transport
550+
551+
if args.path is None:
552+
raise CommandError("romfs deploy: source path not given")
553+
554+
rom_id = args.partition
555+
romfs_filename = args.path
556+
557+
# Read in or create the ROMFS filesystem image.
558+
if romfs_filename.endswith(".romfs"):
559+
with open(romfs_filename, "rb") as f:
560+
romfs = f.read()
561+
else:
562+
romfs = make_romfs(romfs_filename, mpy_cross=args.mpy)
563+
print(f"Image size is {len(romfs)} bytes")
564+
565+
# Detect the ROMFS partition and get its associated device.
566+
state.transport.exec("import vfs")
567+
if not state.transport.eval("hasattr(vfs,'rom_ioctl')"):
568+
raise CommandError("ROMFS is not enabled on this device")
569+
transport.exec(f"dev=vfs.rom_ioctl(2,{rom_id})")
570+
if transport.eval("isinstance(dev,int) and dev<0"):
571+
raise CommandError(f"ROMFS{rom_id} partition not found on device")
572+
has_object = transport.eval("hasattr(dev,'ioctl')")
573+
if has_object:
574+
rom_block_count = transport.eval("dev.ioctl(4,0)")
575+
rom_block_size = transport.eval("dev.ioctl(5,0)")
576+
rom_size = rom_block_count * rom_block_size
577+
print(
578+
f"ROMFS{rom_id} partition has size {rom_size} bytes ({rom_block_count} blocks of {rom_block_size} bytes each)"
579+
)
580+
else:
581+
rom_size = transport.eval("len(dev)")
582+
print(f"ROMFS{rom_id} partition has size {rom_size} bytes")
583+
584+
# Check if ROMFS filesystem image will fit in the target partition.
585+
if len(romfs) > rom_size:
586+
print("ROMFS image is too big for the target partition")
587+
sys.exit(1)
588+
589+
# Prepare ROMFS partition for writing.
590+
print(f"Preparing ROMFS{rom_id} partition for writing")
591+
transport.exec("import vfs\ntry:\n vfs.umount('/rom')\nexcept:\n pass")
592+
chunk_size = 4096
593+
if has_object:
594+
for offset in range(0, len(romfs), rom_block_size):
595+
transport.exec(f"dev.ioctl(6,{offset // rom_block_size})")
596+
chunk_size = min(chunk_size, rom_block_size)
597+
else:
598+
rom_min_write = transport.eval(f"vfs.rom_ioctl(3,{rom_id},{len(romfs)})")
599+
chunk_size = max(chunk_size, rom_min_write)
600+
601+
# Detect capabilities of the device to use the fastest method of transfer.
602+
has_bytes_fromhex = transport.eval("hasattr(bytes,'fromhex')")
603+
try:
604+
transport.exec("from binascii import a2b_base64")
605+
has_a2b_base64 = True
606+
except TransportExecError:
607+
has_a2b_base64 = False
608+
try:
609+
transport.exec("from io import BytesIO")
610+
transport.exec("from deflate import DeflateIO,RAW")
611+
has_deflate_io = True
612+
except TransportExecError:
613+
has_deflate_io = False
614+
615+
# Deploy the ROMFS filesystem image to the device.
616+
for offset in range(0, len(romfs), chunk_size):
617+
romfs_chunk = romfs[offset : offset + chunk_size]
618+
romfs_chunk += bytes(chunk_size - len(romfs_chunk))
619+
if has_deflate_io:
620+
# Needs: binascii.a2b_base64, io.BytesIO, deflate.DeflateIO.
621+
romfs_chunk_compressed = zlib.compress(romfs_chunk, wbits=-9)
622+
buf = binascii.b2a_base64(romfs_chunk_compressed).strip()
623+
transport.exec(f"buf=DeflateIO(BytesIO(a2b_base64({buf})),RAW,9).read()")
624+
elif has_a2b_base64:
625+
# Needs: binascii.a2b_base64.
626+
buf = binascii.b2a_base64(romfs_chunk)
627+
transport.exec(f"buf=a2b_base64({buf})")
628+
elif has_bytes_fromhex:
629+
# Needs: bytes.fromhex.
630+
buf = romfs_chunk.hex()
631+
transport.exec(f"buf=bytes.fromhex('{buf}')")
632+
else:
633+
# Needs nothing special.
634+
transport.exec("buf=" + repr(romfs_chunk))
635+
print(f"\rWriting at offset {offset}", end="")
636+
if has_object:
637+
transport.exec(
638+
f"dev.writeblocks({offset // rom_block_size},buf,{offset % rom_block_size})"
639+
)
640+
else:
641+
transport.exec(f"vfs.rom_ioctl(4,{rom_id},{offset},buf)")
642+
643+
# Complete writing.
644+
if not has_object:
645+
transport.eval(f"vfs.rom_ioctl(5,{rom_id})")
646+
647+
print()
648+
print("ROMFS image deployed")
649+
650+
651+
def do_romfs(state, args):
652+
if args.command[0] == "query":
653+
_do_romfs_query(state, args)
654+
elif args.command[0] == "build":
655+
_do_romfs_build(state, args)
656+
elif args.command[0] == "deploy":
657+
_do_romfs_deploy(state, args)
658+
else:
659+
raise CommandError(
660+
f"romfs: '{args.command[0]}' is not a command; pass romfs --help for a list"
661+
)

tools/mpremote/mpremote/main.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
do_resume,
3737
do_rtc,
3838
do_soft_reset,
39+
do_romfs,
3940
)
4041
from .mip import do_mip
4142
from .repl import do_repl
@@ -228,6 +229,32 @@ def argparse_mip():
228229
return cmd_parser
229230

230231

232+
def argparse_romfs():
233+
cmd_parser = argparse.ArgumentParser(description="manage ROM partitions")
234+
_bool_flag(
235+
cmd_parser,
236+
"mpy",
237+
"m",
238+
True,
239+
"automatically compile .py files to .mpy when building the ROMFS image (default)",
240+
)
241+
cmd_parser.add_argument(
242+
"--partition",
243+
"-p",
244+
type=int,
245+
default=0,
246+
help="ROMFS partition to use",
247+
)
248+
cmd_parser.add_argument(
249+
"--output",
250+
"-o",
251+
help="output file",
252+
)
253+
cmd_parser.add_argument("command", nargs=1, help="romfs command, one of: query, build, deploy")
254+
cmd_parser.add_argument("path", nargs="?", help="path to directory to deploy")
255+
return cmd_parser
256+
257+
231258
def argparse_none(description):
232259
return lambda: argparse.ArgumentParser(description=description)
233260

@@ -302,6 +329,10 @@ def argparse_none(description):
302329
do_version,
303330
argparse_none("print version and exit"),
304331
),
332+
"romfs": (
333+
do_romfs,
334+
argparse_romfs,
335+
),
305336
}
306337

307338
# Additional commands aliases.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy