-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Define vfs.rom_ioctl()
, add romfs commands to mpremote, and implement ROM partition support on stm32, rp2, esp32, esp8266
#16857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #16857 +/- ##
=======================================
Coverage 98.54% 98.54%
=======================================
Files 169 169
Lines 21864 21877 +13
=======================================
+ Hits 21545 21558 +13
Misses 319 319 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code size report:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is neat! I was able to play around with it on esp8266 quite easily, and the deploy cycle looks like it should be pretty fast unless you have a ton of .mpy files!
I left some comments but they're all relatively minor things, I think could address in a follow-up if you want to merge this sooner.
docs/reference/mpremote.rst
Outdated
@@ -347,6 +348,29 @@ The full list of supported commands are: | |||
This happens automatically when ``mpremote`` terminates, but it can be used | |||
in a sequence to unmount an earlier mount before subsequent command are run. | |||
|
|||
.. _mpremote_command_romfs: | |||
|
|||
- **romfs** -- manage ROM partitions on the device: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- **romfs** -- manage ROM partitions on the device: | |
- **romfs** -- manage ROMFS partitions on the device: | |
.. note:: This feature is currently experimental and only enabled on some ports and boards, via the `MICROPY_VFS_ROM` build flag. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related, I noticed in some of the mpremote error messages that the terms "ROM partition" and "ROMFS partition" are used interchangeably. Is there a meaningful distinction for the user? As "ROM" is already a very overloaded term then it might be clearer to only talk about "ROMFS" and "ROMFS partition",
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks.
I forgot to mention this in the PR description. I did want to have some distinction between ROM being the place in memory and ROMFS being the thing that goes in that place.
Because maybe in the future the ROM (partitions) can be used for something else, not just putting a ROMFS in them. Well even now that's possible, the vfs.rom_ioctl()
call is essentially giving the user a place to store things, that's non-volatile and can be mapped to memory. (It's kind of adding a cross-port way of accessing internal flash, like pyb.Flash
and rp2.Flash
etc already do.) And that's why I called it rom_ioctl()
and not romfs_ioctl()
. Because this function call has nothing to do with ROMFS (it's at a layer lower).
But as you say, ROM is quite overloaded, so maybe it's worth calling everything ROMFS even if it's not used for a VfsRom
filesystem?
Maybe then the function should indeed be called vfs.romfs_ioctl()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think rom_ioctl()
is OK, my comment was more around standardising the docs and help strings so there's no confusion about whether "ROM partition" and "ROMFS partition" are different types of things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've now changed all "ROM" to "ROMFS". There's now a distinction between "ROMFS partition" and "ROMFS image" and it's used consistently in the docs and mpremote.
ports/qemu/main.c
Outdated
@@ -77,3 +77,7 @@ void nlr_jump_fail(void *val) { | |||
mp_printf(&mp_plat_print, "uncaught NLR\n"); | |||
exit(1); | |||
} | |||
|
|||
mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { | |||
return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); | |
return MP_OBJ_NEW_SMALL_INT(-MP_ENODEV); |
... maybe nitpicky, I don't know if this is worth distinguishing? That's the closest OS error code I can see for NotImplementedError.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that EINVAL means invalid ioctl.
But probably it should at least implement MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS
and return 0 for that.
Then the rest of the ioctls are invalid and return -MP_EINVAL
.
?
Edit: changed MP_ENODEV
to MP_EINVAL
in the last sentence above, that's what I intended to write.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess my thinking was more:
- This isn't implemented at all - MP_ENODEV
- This is implemented but the ioctl you asked for is invalid here - MP_EINVAL
But I think it's fuzzy enough to use MP_EINVAL everywhere, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been resolved by adding a MICROPY_VFS_ROM_IOCTL
config option and disabling that on qemu and unix, so this function is not even needed anymore.
But then I did add it on unix for the coverage build, in a separate commit here.
@@ -803,3 +804,7 @@ void nlr_jump_fail(void *val) { | |||
fprintf(stderr, "FATAL: uncaught NLR %p\n", val); | |||
exit(1); | |||
} | |||
|
|||
mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { | |||
return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); | |
return MP_OBJ_NEW_SMALL_INT(-MP_ENODEV); |
(Same caveat as above.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved as above.
tools/mpremote/mpremote/commands.py
Outdated
state.transport.exec("import vfs") | ||
num_rom_partitions = state.transport.eval("hasattr(vfs, 'rom_ioctl') and vfs.rom_ioctl(1)") | ||
if num_rom_partitions is False: | ||
print("No ROM partitions available") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to distinguish between "No ROM partitions available" and "ROMFS support is not enabled in this MicroPython build"? IIUC on some ports there is is a meaningful difference, which effects what a user would do if they want to use romfs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, failure here and elsewhere in this function should probably result in a non-zero process exit code for scripting use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to distinguish between "No ROM partitions available" and "ROMFS support is not enabled in this MicroPython build"?
Yes, will do that. ROMFS is supported if vfs.rom_ioctl()
exists. Then can use rom_ioctl(1)
to get the number of partitions.
Also, failure here and elsewhere in this function should probably result in a non-zero process exit code for scripting use
This particular one is the result of romfs query
, which should just print out info, not have an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mpromet now distinguishes between rom_ioctl
existing or not, and a ROMFS partition being there or not.
tools/mpremote/mpremote/commands.py
Outdated
elif args.command[0] == "deploy": | ||
_do_romfs_deploy(state, args) | ||
else: | ||
raise CommandError(f"romfs: '{args.command[0]}' is not a command") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise CommandError(f"romfs: '{args.command[0]}' is not a command") | |
raise CommandError(f"romfs: '{args.command[0]}' is not a command. Pass romfs --help for a list.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
tools/mpremote/mpremote/main.py
Outdated
"-o", | ||
help="output file", | ||
) | ||
cmd_parser.add_argument("command", nargs=1, help="romfs command (e.g. query, build, deploy)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cmd_parser.add_argument("command", nargs=1, help="romfs command (e.g. query, build, deploy)") | |
cmd_parser.add_argument("command", nargs=1, help="romfs command. Supported commands: query, build, deploy.") |
(As this is the only place the tool actually prints the list of accepted commands, I think.)
The other way to improve this would be to add another argparse sub-command parser so we get generated list of commands and their associated options, but that seems like something that can be done later when there are more sub-commands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I struggled a bit with this sub-command parsing. Was trying to copy how fs <subcommand>
works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Help message adjusted.
tools/mpremote/mpremote/main.py
Outdated
@@ -228,6 +229,26 @@ def argparse_mip(): | |||
return cmd_parser | |||
|
|||
|
|||
def argparse_romfs(): | |||
cmd_parser = argparse.ArgumentParser(description="manage ROM partitions") | |||
_bool_flag(cmd_parser, "mpy", "m", True, "download as compiled .mpy files (default)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_bool_flag(cmd_parser, "mpy", "m", True, "download as compiled .mpy files (default)") | |
_bool_flag(cmd_parser, "mpy", "m", True, "Automatically compile .py files to .mpy when buliding the ROMFS (default)") |
(Suggested rewording as the verb "download" is unclear in this context.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworded as suggested.
mp_obj_t romfs = mp_call_function_1(MP_OBJ_FROM_PTR(&mp_type_vfs_rom), rom); | ||
mp_obj_t mount_point = MP_OBJ_NEW_QSTR(MP_QSTR__slash_rom); | ||
mp_call_function_2(MP_OBJ_FROM_PTR(&mp_vfs_mount_obj), romfs, mount_point); | ||
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_rom_slash_lib)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got caught out that I couldn't import a Python module from the top-level of the rootfs without doing sys.path.append('/rom')
. How about adding both /rom
and /rom/lib
to sys.path by default?
(I'm thinking along the lines that the top-level is the application code either as .py files or package directories, and /rom/lib
is managed by some future variant of 'mip install'.)
I don't really see a downside - I guess someone might decide to put a lot of non-Python assets in the top level and slow down import a little, but that seems like a much less common pattern than putting a Python file there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding to the search path, ie sys.path
, really does slow down imports. Because you need to stat .py
and .mpy
for each import (assuming the first fails), and then for packages you also need to stat __init__.py
and __init__.mpy
. This is something we need to optimise in general.
So, I really wanted to keep sys.path
down to a minimum. I did start out adding just /rom
to the path, but after discussion with you and Jim, changed that to /rom/lib
.
The user can easily change the path themselves.
Note that neither boot.py
nor main.py
will execute from the ROMFS, because those boot up files are searched in the current directory.
I'd really like to improve all this in MicroPython 2.0. But for now... maybe just keep it as /rom/lib
and document it? Or change it to '/rom`? Or just omit altogether and make the user add it as they need?
Side note: mip install
won't work with /rom/lib
in the path before /flash/lib
, because it tries to install into the ROMFS...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess a key question is what behaviour we want for (eventually) installing MIP packages into the ROMFS. I'd figured that it's desirable to keep them separated somewhere, so it's easy for the developer to see the difference between "my application" and "libraries I've installed from elsewhere". So my thinking was that /rom/lib
would be the natural place to put the externa llibraries, as it mirrors the way it works now.
I don't think there's any way to have this distinction and only add one entry to the path, though...
Or just omit altogether and make the user add it as they need?
On balance I think it's preferable to have helpful/usable defaults. Then the developer who wants to optimise their package import speed can be the one who tweaks sys.path to remove entries they don't need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(But if we're going with one default entry only, probably /rom
is less surprising for people building ROMFS from a directory and then using it on their board.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On balance I think it's preferable to have helpful/usable defaults. Then the developer who wants to optimise their package import speed can be the one who tweaks sys.path to remove entries they don't need
Yes, that's a good point. So maybe then the defaults can be /rom
and /rom/lib
, which is how we recommend to layout a project. Then users can tweak as needed.
I'll do a few benchmarks to see if adding a second path makes much of a difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both /rom
and /rom/lib
are now added automatically to sys.path
.
e6fa0fb
to
ffcc0f6
Compare
I added some coverage testing to get CI to pass. |
I took #8381 and pulled out some things, then cleaned things up, to get this PR. After this is merged I will rebase/rework #8381, which will add all the remaining functionality (but probably not until after 1.25.0 release). So this PR is not really in addition to or a replacement. It's more of a precursor. |
I'm also a bit confused by this PR. Will ROMFS work if only this PR gets merged in 1.25.0? If so, what will be missing? Also, in #8381 I sent commits to enable ROMFS for Arduino boards, is it possible to cherry-pick them for inclusion in 1.25.0? They're pretty much the same as PYBD_SFx boards. |
I've built an RP2 firmware with this PR and VFSROM was available. So it's in a working state. I have to check whether the drivers for SAMD, MIMXRT and NRF work. They may need some tuning. |
@iabdalkader As a test I added a subset of the driver changes for SAMD51 to this PR just for one SAMD51 board, and VFSROM is available. That makes me confident that adapting these is straightforward. Thanks, Damien. |
@robert-hh Thanks for testing! I was hoping |
In addition I just cherry-picked the three commits from PR #8381. That works. Only the path setting done in _boot.py seems to get lost. It seems that these lines are not needed any more. |
I ran some tests to time how long Below are all the stat'ing that The test does PYBD_SF2
['', '.frozen', '/rom', '/rom/lib', '/flash', '/flash/lib'] With an empty ROMFS (exists but has no files in it).
With a ROMFS with 25 files in
ESP8266
['', '.frozen', '/rom', '/rom/lib', '/lib', '/'] With an empty ROMFS:
With a ROMFS with 25 files in
The tests above show that it's significantly faster to stat from ROMFS (which is good!) than from a FAT filesystem (and littlefs is about the same speed as FAT). Even searching frozen code can be slower than ROMFS (esp8266 had about 25 frozen modules, and it does a linear search through that list). So I think we can safely add both |
ffcc0f6
to
7364a2d
Compare
ROMFS already can work because What this PR adds is (as mentioned in the top message) What's missing in this PR:
So, that work is left for #8381.
We can add support for mimxrt in a follow up PR.
We can also enable it on those boards.
This ROMFS mounting logic is now handled automatically (in C) when you call |
I've updated based on all the feedback. @iabdalkader I'll do a follow-up PR to enable ROMFS on the Arduino boards. |
@dpgeorge Thanks! I checked and they're exactly the same as PYBD_SFx support, so that much hasn't changed. |
This allows defining a `memoryview` instance, either statically or on the C stack. Signed-off-by: Damien George <damien@micropython.org>
This is a generic interface to allow querying and modifying the read-only memory area of a device, if it has such an area. Signed-off-by: Damien George <damien@micropython.org>
This function will attempt to create a `VfsRom` instance and mount it at location "/rom" in the filesystem. Signed-off-by: Damien George <damien@micropython.org>
This is put in `mp_init()` to make it consistent across all ports. Signed-off-by: Damien George <damien@micropython.org>
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>
This commit implements `vfs.rom_ioctl()` to query, erase and write both internal and external flash, depending on how the board configures its flash memory. A board can configure ROM as follows. To use internal flash memory: #define MICROPY_HW_ROMFS_ENABLE_INTERNAL_FLASH (1) To use external flash memory (QSPI memory mapped): #define MICROPY_HW_ROMFS_ENABLE_EXTERNAL_QSPI (1) #define MICROPY_HW_ROMFS_QSPI_SPIFLASH_OBJ (&spi_obj) Then the partition must be defined as symbols in the linker script: _micropy_hw_romfs_part1_start _micropy_hw_romfs_part1_size And finally the partition needs to be enabled: #define MICROPY_HW_ROMFS_ENABLE_PART1 (1) There's support for a second, optional partition via: _micropy_hw_romfs_part2_start _micropy_hw_romfs_part2_size #define MICROPY_HW_ROMFS_ENABLE_PART1 (1) Signed-off-by: Damien George <damien@micropython.org>
Using unused and previously inaccessible external QSPI flash. Signed-off-by: Damien George <damien@micropython.org>
Not enabled by default on any board. A board can enable a ROMFS partition by defining `MICROPY_HW_ROMFS_BYTES` in its `mpconfigboard.h` file. For example: #define MICROPY_HW_ROMFS_BYTES (128 * 1024) The ROMFS partition is placed at the end of the flash allocated for the firmware, giving less space for the firmware. It then lives between the firmware and the read/write filesystem. Signed-off-by: Damien George <damien@micropython.org>
Not enabled by default on any board. For a board to enable ROMFS it must: - Add `#define MICROPY_VFS_ROM (1)` to its `mpconfigboard.h` file. - Use `partitions-4MiB-romfs.csv` as its partitions file (or a similar partitions definition that has an entry labelled "romfs"). Signed-off-by: Damien George <damien@micropython.org>
Not enabled by default on any board. For a board to enable ROMFS it must: - Add `#define MICROPY_VFS_ROM (1)` to its `mpconfigboard.h` file. - Add a FLASH_ROMFS partition to the linker script and expose the partition with: _micropy_hw_romfs_start = ORIGIN(FLASH_ROMFS); _micropy_hw_romfs_size = LENGTH(FLASH_ROMFS); Signed-off-by: Damien George <damien@micropython.org>
The same as the 2M flash variant but with a 320KiB ROM partition. Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
7364a2d
to
be0fce9
Compare
Thank you for providing this feature. Should I make a PR for the MIMXRT, SAMD, NRF and RENESAS port support it, or will you collect these files from PR #8381? |
Wouldn't there be many merge conflicts? |
What is the state of adding VFSROM to the MIMXRT, SAMD, NRF and RENESAS port? |
Summary
This is a continuation of #8381 to add general ROMFS support to the ports.
So far, we have the
vfs.VfsRom
filesystem format defined and driver implemented. That was merged via #16446. That allows creating and mounting a ROMFS filesystem if you have the ROMFS image in memory somewhere.But there's not yet any way to use the ROMFS on the ports, because they don't have a way to get the ROMFS image on them, or access it.
This PR adds support for ROMFS on stm32, rp2, esp32 and esp8266.
I wanted to keep this PR relatively self-contained and minimal (although it's still rather large), so it's easier to review. Then further work can be done later on, eg to add support to more ports. PR #8381 does contain implementations of the remaining ports.
This PR has the following:
vfs.rom_ioctl()
which is used as a general interface to access the read-only memory of a device. It can query, erase and write this memory./rom
in the filesystem.MICROPY_VFS_ROM
is enabled, there is automatic mounting of the first ROM partition (if found usingvfs.rom_ioctl
) at the end of the call tomp_init()
, so that all ports behave the same way. This also makes it much easier to enable the ROMFS feature, without adding anything to_boot.py
(which is hard to do conditionally).mpremote
commands which usevfs.rom_ioctl()
to query, build and deploy ROMFS images.mpremote
commands.vfs.rom_ioctl()
in the following ports: stm32, rp2, esp32, esp8266.Note that no other boards have a ROMFS partition enabled by default, although it's possible to enable it yourself by modifying the board definition (or creating your own board definition with it enabled). The reason for not enabling it by default on more boards is because it's a breaking change for the user, since it allocates a lot of flash to the ROMFS and takes it away from the firmware and possible frozen code. In the future we can work out how to migrate all boards to have a ROMFS, but for now that's left as an unsolved problem.
Testing
Tested on PYBD_SF2, PYBD_SF6, esp8266 using the new variant.
Also tested on rp2 and esp32 boards by modifying the board definition as described in the commit messages for those ports.
Trade-offs and Alternatives
This is a big new feature! It's hard to get big things right first go so that's why I wanted to take smaller steps to add independent parts at a time. Also thanks to @projectgus for advice in this regard.
For now ROMFS is an advanced feature and most users would need to modify board definitions, or create custom ones, to use it. Alternatives would be to just enable it on all boards and provide some docs for users freezing a lot of code to migrate to the new system. Or, postpone this feature even longer until we can work out how to better configure the ROMFS partitions. But that will delay things even more and this feature has been in the works for years now.