8000 stm32: littlefs support by dpgeorge · Pull Request #5330 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

stm32: littlefs support #5330

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

Merged
merged 7 commits into from
Nov 25, 2019
Merged

Conversation

dpgeorge
Copy link
Member

This PR aims to add littlefs support to the stm32 port. It builds upon #5299 (generalise flash mounting code in stm32); see #3847, #5167 and #5249 for prior attempts at stm32+littlefs.

The commits here do a few things:

  • Extend the pyb.Flash() class so it can represent sections/partitions of the flash. Eg pyb.Flash(start=0, len=1024*1024) creates a 1MiB block device at the start of the flash.
  • Extend the pyb.Flash() class to support the extended block protocol.
  • Add support to stm32's main so it can automatically detect the first filesystem on the flash (even if the FS doesn't use all the flash) and mount it appropriately.

With this PR one can use littlefs on PYBv1.x (using just internal flash, 512 byte blocks), and on PYBD (using external SPI flash, 4k byte blocks). Other boards can also be supported, with internal or external flash, but they don't have the new features enabled.

On PYBv1.x and PYBD to switch the entire storage to a littlefs filesystem do:

>>> import pyb, os
>>> fl = pyb.Flash(start=0) # create object accessing entire flash
>>> os.VfsLfs2.mkfs(fl)

On PYBD to create two partitions/filesystems, the first being a FAT the second being a littlefs, do:

>>> import pyb, os
>>> fl1 = pyb.Flash(start=0, len=1024*1024) # 1MiB partition
>>> fl2 = pyb.Flash(start=1024*1024) # remaining 1MiB
>>> os.VfsFat.mkfs(fl1) # format FAT on partition 1
>>> os.VfsLfs2.mkfs(fl2) # format littlefs2 on partition2

The first filesystem will be mounted automatically on boot at /flash (retains old behaviour), whether it's a FAT or LFS. You can then mount any other filesystems manually, eg for PYBD example just above:

os.mount(pyb.Flash(start=1024*1024), '/lfs')

There is a little bit of "magic" behind the scenes to select either 512 or 4096 byte block sizes when using FAT vs LFS. All it is is that littlefs passes a flag along to the block device when initialising it, to indicate that it will use the extended block device. The flash block device then configures itself for 4k block sizes. (The point is that for stm32 the flash storage originally had 512 byte blocks, and now it needs to support both 512 byte and 4096 byte blocks to accommodate both FAT and LFS efficiently.)

IMO this is the "simplest" set of changes that makes littlefs "just work" on stm32, with the goals that 1) the user can dynamically select FAT or LFS by simply formatting as desired; 2) 512 byte blocks are still used for FAT, and 4096 byte blocks are used for LFS.

@hoihu
Copy link
Contributor
hoihu commented Nov 14, 2019

looking forward to this, great.

Btw would there be a way to add a Vfs layer on top of littlefs so that it can be exposed as MSC over USB too? or is this not feasable?

I‘m having no problem if it is not possible, just curious.

@dpgeorge
Copy link
Member Author

Btw would there be a way to add a Vfs layer on top of littlefs so that it can be exposed as MSC over USB too? or is this not feasable?

It may be feasible using the "ghost FAT" scheme https://github.com/microsoft/uf2-nrf5/blob/master/src/ghostfat.c but it's a lot of work and may in the end not be reliable enough.

For now I'd suggest looking at ways to actually read a littlefs on your PC, eg https://github.com/ARMmbed/littlefs-fuse

// populate the filesystem with factory files
factory_reset_make_files(&vfs_fat->fatfs);
#if MICROPY_VFS_LFS2
if (memcmp(&buf[8], "littlefs", 8) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these strings at magic locations part of the official lfs1/2 header formats?

If so, this chunk of code the check the format of the block device would be a good candidate to move to common place for all the ports to use!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found them out by inspecting the littlefs code, the superblocks.

Can certainly move it to a common location for general use.

@dpgeorge
Copy link
Member Author

@andrewleech I know this misses a lot of the things you added in your PRs on littlefs, in particular compile-time configurability of what FS type and partition to use by default (this PR still defaults to FAT FS). But my main aim was to make it simple, and dynamically configurable to start with, so the user can easily select their FS type.

Is there anything critical missing from here that you need, or could you work with this PR as-is?

@andrewleech
Copy link
Contributor

I wholehartedly agree with simplifying the merge changesets, it makes sense to have the smaller changes that keep exising things working. I've been trying to split up my stuff into more sensible commits but not making much progress lately. I've been thinking of ways to simplify my spiflash changes to work on all ports equally and to not need to changes as much, as well as reduce the overheads I've currently added. I'll get back to that at some stage.

What you've got here looks good to me, haven't had a chance to test it yet though.

Looking through it I think this actually would support all of my current use case. I've got qspi flash as main storage (no internal flash used), with half of it configured as fatfs, the other half as littlefs.
With this changeset I should be able to have the first half #defined in mpconfigboard the same as it currently is (same as pybd) with the appropriate length, then the second half can be defined in boot.py as littlefs and mounted appropriately.

return false;
}
uint8_t buf[FLASH_BLOCK_SIZE];
storage_read_blocks(buf, 0x100, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x100 should probably be FLASH_PART1_START_BLOCK

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


if (ret == -MP_ENODEV && reset_mode != 3) {
// No filesystem (and didn't already create one), try to create a fresh one
ret = factory_reset_create_filesystem();
Copy link
Member
@jimmo jimmo Nov 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a case here where the above code can detect LFS, but mount fails. In which case bdev will now be re-initialised by the len != -1 check above, but should now be reset back to the pyb_flash_obj singleton.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed: this code will now only run if bdev==pyb_flash_obj, which reduces the chance that it will accidentally wipe a littlefs filesystem

uint32_t offset = mp_obj_get_int(args[3]);
#if MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE
block_num += self->start / FLASH_BLOCK_SIZE;
ret = flash_bdev_readblocks_ext(bufinfo.buf, block_num, offset, bufinfo.len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

storage_read_blocks returns unsigned, but flash_bdev_readblocks_ext (and spi_bdev_readblocks_raw) return signed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, storage_read_blocks now returns an int

block_num += FLASH_PART1_START_BLOCK + (int32_t)self->start / FLASH_BLOCK_SIZE;
ret = storage_read_blocks(bufinfo.buf, block_num, bufinfo.len / FLASH_BLOCK_SIZE);
} else {
uint32_t offset = mp_obj_get_int(args[3]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mp_vfs_autodetect will call the 4-arg version of this, so when this happens on the singleton default pyb_flash_obj it ends up calculating an invalid block.

convert_block_to_flash_addr returns -1 (in an unsigned), which then results in this function returning (unsigned)-1 as a small int. But mp_vfs_autodetect ignores the return value anyway, reads the uninitialised buffer, then ends up defaulting to flash.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed by returning an error if 4 args are used with pyb_flash_obj

mp_int_t n;
if (self == &pyb_flash_obj) {
// Get true size
n = storage_get_block_count();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worth adding a comment where pyb_flash_obj is defined explaining that it has self->len set to zero (because it's explicitly handled here).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@andrewleech
Copy link
Contributor

I'm trying to test this on my qspi-flash board, running into a problem with the start offsets.

Starting with init_flash_fs, storage_read_blocks(buf, 0x100, 1) seems to work fine, flowing down the call stack with the block/address += FLASH_PART1_START_BLOCK and then
-= FLASH_PART1_START_BLOCK at the appropriate places to cancel out the virtual offset.

After that, I'm getting to vfs_mount_and_chdir which calls mp_vfs_autodetect then
mp_vfs_blockdev_read_ext(&blockdev, 0, 8, sizeof(buf), buf);

This resolves to pyb_flash_readblocks() with block_num==0 and offset==8

Later in that function however;

#elif defined(SPIFLASH)
block_num += self->start / MP_SPIFLASH_ERASE_BLOCK_SIZE;

where
self->start == -(FLASH_PART1_START_BLOCK * FLASH_BLOCK_SIZE)
which ends up with mp_spiflash_read() being called with the address 0xfffe0008, locking up the qspi.

@jimmo
Copy link
Member
jimmo commented Nov 19, 2019

With the change to use mp_vfs_autodetect (via vfs_mount_and_chdir -> mp_vfs_mount) instead of pyb_flash_init_vfs, the "native" shortcut for the vfs->bdev interface is no longer configured. Might be worth just stripping out MP_BLOCKDEV_FLAG_NATIVE altogether and saving the bytes (my quick estimate is about 80 bytes).

@dpgeorge
Copy link
Member Author

which ends up with mp_spiflash_read() being called with the address 0xfffe0008, locking up the qspi.

This looks exactly like the problem @jimmo flagged above, that the auto-detection acting on the default pyb_flash_obj will read an invalid block number.

Probably best if pyb_flash_readblocks (and writeblocks) return an error if access would be outside the valid range.

@dpgeorge
Copy link
Member Author

Probably best if pyb_flash_readblocks (and writeblocks) return an error if access would be outside the valid range.

I pushed a different fix, which just checks if the object is pyb_flash_obj combined with an extended read/write, and returns an error . @andrewleech would be good if you could test with this latest commit.

@dpgeorge dpgeorge force-pushed the stm32-support-littlefs branch from fd382bf to c070d02 Compare November 25, 2019 08:04
The pyb.Flash() class can now be used to construct objects which reference
sections of the flash storage, starting at a certain offset and going for a
certain length.  Such objects also support the extended block protocol.
The signature for the constructor is: pyb.Flash(start=-1, len=-1).
To hint to the block device that the extended block protocol will be used.
And return -MP_EIO if calling storage_read_block/storage_write_block fails.
This lines up with the return type and value (negative for error) of the
calls to MICROPY_HW_BDEV_READBLOCKS (and WRITEBLOCKS, and BDEV2 versions).
@dpgeorge dpgeorge force-pushed the stm32-support-littlefs branch from c070d02 to 6b3404f Compare November 25, 2019 13:09
@dpgeorge dpgeorge changed the title stm32: littlefs support (WIP) stm32: littlefs support Nov 25, 2019
@dpgeorge dpgeorge merged commit 6b3404f into micropython:master Nov 25, 2019
@dpgeorge dpgeorge deleted the stm32-support-littlefs branch November 25, 2019 13:24
@dpgeorge
Copy link
Member Author
dpgeorge commen F438 ted Nov 25, 2019

This was cleaned up and merged.

@andrewleech
Copy link
Contributor

Thanks for this! I didn't get a chance to test again but I'll follow up with feedback if needed when I do soon.

@dpgeorge
Copy link
Member Author

@andrewleech probably the next thing to look at adding/merging would be support for 32-bit SPI flash addressing. But no hurry.

@andrewleech
Copy link
Contributor
andrewleech commented Nov 25, 2019

Yes I've got a newer cleaner version of that ready for testing (already based on top of a previous verions of this PR). I'll try to slice it into my workload this week.

I think that's the only thing blocking me from completely switching over to this new littlefs implementation.

@andrewleech
Copy link
Contributor

FYI for mass storage use this might help windows users: https://bluscape.blog/2019/10/01/littlefs-explorer-lfse-for-windows/

tannewt pushed a commit to tannewt/circuitpython that referenced this pull request Sep 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

4 participants
0