Skip to content
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

After 'import flash', 'flash.erase()' does not appear work on Amigo TFT. #233

Closed
jdlcdl opened this issue Aug 28, 2023 · 25 comments
Closed

Comments

@jdlcdl
Copy link
Collaborator

jdlcdl commented Aug 28, 2023

This issue is much longer than I initially intended. TL;DR it's not a concerning issue at all, I'm simply sharing.

In my quest to be able to explain every byte on my Amigo's 16MB onboard SPI Flash storage, I was digging into the 3MB of SPIFFS between 0xd00000 and the end of flash. I found many copies of settings.json spread sparsely in this space, as well as some 4-byte markers (spaced 131072 bytes apart) that I assume are formatting. I wanted to start with a clean slate. While os.remove() and os.flash_format() work to remove files from SPIFFS, they're certainly not clearing data bytes; I didn't truly expect that they would.

I recall that in src/krux/firmware.py flash is written via import flash with a flash.erase() step prior to flash.write() here on the develop_rc branch (and that it did so two lines earlier on the main branch) but when I tried to use it to erase blocks in SPIFFS space I had no luck. I didn't know if SPIFFS was silently protecting me from direct access to the bytes owned by that filesystem, so I tried to use flash.write() and flash.read() to verify that it does not. I did the same outside of SPIFFS and had success each time, however flash.erase() never worked for me anywhere.

I am assuming that import flash is made possible via MaixPy/components/micropython/port/src/flash and a w25qxx.h header in MaixPy/components/drivers/flash/include. Reviewing those informed me that I should only be using flash.erase() for 4096 and 65536 sized blocks at aligned addresses, as src/krux/firmware.py does, but I had already tried this to no avail. I also tried other block sizes and did NOT get any exceptions, which surprised me. Maybe my assumption at the beginning of this paragraph is wrong.

I was able to find a related discussion here but it was more about resolving a compile-time error and not about whether flash.erase() worked or not.

Maybe flash.erase() works, and/or is necessary on some boards, and isn't supported on others? I have found other w25q* drivers that use the same 0x20 and 0xD8 for erasing 4096 and 65536 sized blocks just as the krux header suggests, as well as some that do not even implement erasing blocks.

For my short term goal, I'm assuming that the flash.erase() step in src/krux/firmware.py is doing nothing for my amigo, and that the flash.write() steps are working just fine, so I have resorted to the following to erase my SPIFFS, which I paste and call from the usb console:

def erase_spiffs():
    '''
    writes 0xff to entire SPIFFS because it appears flash.erase() doesn't work on Amigo TFT

    assumes `import flash` origin is selfcustody/MaixPy/components/micropython/port/src/flash
    '''

    FLASH_SIZE = 2**24
    SPIFFS_ADDR = 0xd00000
    BLOCK_SIZE = 0x10000

    import flash

    empty_buf = b'\xff' * BLOCK_SIZE
    for address in range(SPIFFS_ADDR, FLASH_SIZE, BLOCK_SIZE):
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            continue

        flash.erase(address, BLOCK_SIZE)
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            print('%d bytes at %s erased' % (BLOCK_SIZE, hex(address)))
            continue
 
        flash.write(address, empty_buf)
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            print('%d bytes at %s written w/ 0xff' % (BLOCK_SIZE, hex(address)))
            continue

I've left it here in case others with different boards would like to confirm. If the messages end with "erased", then flash.erase() indeed is working for some boards.

Note: This only works because SPIFFS is the very end of the 16MB flash, if SPIFFS were one byte larger, or moved one byte further, it could overflow into Kboot stage 0 and brick the Amigo until battery discharged, then would require ktool reflash.

In past discussions that were more focused around not bricking devices due to overflow here, It was opined that in regards to SPIFFS overflowing, we might just limit the number of encrypted seeds stored there. Now that I have looked and found SPIFFS to be sparse and cluttered w/ old data, and with formatting markers throughout, I suspect that SPIFFS is respectful of its 0x300000 size and that this should not be a concern -- assuming that krux devs are respectful that SPIFFS address + size does not overrun 16MB.

@tadeubas
Copy link
Contributor

Here says that: "SPIFFS is a file system intended for SPI NOR flash devices on embedded targets. It supports wear levelling, file system consistency checks, and more." So I think it is expected multiple copies of the same file throughout all the available space on SPIFFS.

Wikipedia says this on NOR flash: "Erasure must happen a block at a time, and resets all the bits in the erased block back to one. Typical block sizes are 64, 128, or 256 KiB.". So I think it is not that simple to erase a NOR flash memory, maybe it is not properly implemented... a way to do this is by saving zeroes or ones throughout all the available space as you did.

The point is, why erase if we can just overwrite? Maybe SPIFFS just try to save the firmware on the "next available space", but sometimes this leads to error because there was no "available space" to do the this operation and it is left incomplete, then the next type you save the firmware SPIFFS do it the right way... this a bug can be of SPIFFS or MaixPy implementation of it? I don't know 😞

@odudex
Copy link
Member

odudex commented Aug 28, 2023

@tadeubas , I think SPIFFS is not used to save the firmware, it will only be used on the SPIFFS area, above 0xd00000

@jdlcdl
Digging into the code, flash.write() also calls the erase method when necessary to raise bits values to 1 here.
The only difference I've noticed is, when writing, it waits while dma is busy. Could this be a clue why direct flash.erase() is not working properly?
We have the demand to add a "wipe device" feature, probably under settings or tools. Most just want restore default settings and get rid of all mnemonics at once, and for that a simple file deletion would work. But using your code would be true wipe, and data would be in fact vanished. This is very relevant work!
@jreesun , @tadeubas what do you think?
To add a "wipe" feature under tools would be easier, because settings has its own way of working and is not built to call methods. But in other devices is something usually placed under settings...

@tadeubas
Copy link
Contributor

Thx @odudex for the explanation! For this wipe feature, I think under tools would be just fine.

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Aug 28, 2023

Digging into the code, flash.write() also calls the erase method when necessary to raise bits values to 1 here.
The only difference I've noticed is, when writing, it waits while dma is busy. Could this be a clue why direct flash.erase() is not working properly?

It could be, I will certainly take a look; I had not even looked there yet. Thank you for the pointer.

@ghost
Copy link

ghost commented Sep 30, 2023

We have the demand to add a "wipe device" feature, probably under settings or tools. Most just want restore default settings and get rid of all mnemonics at once, and for that a simple file deletion would work. But using your code would be true wipe, and data would be in fact vanished. This is very relevant work!
@jreesun , @tadeubas what do you think?
To add a "wipe" feature under tools would be easier, because settings has its own way of working and is not built to call methods. But in other devices is something usually placed under settings...

A "Restore defaults" option under Settings is a good idea and should be its own feature separate from wiping. You might have stored encrypted mnemonics on the device but accidentally changed some settings and just want to return to the defaults, in which case you wouldn't want to wipe out your mnemonics...

As for "full wipes," it's unfortunate that we can't do this by default now due to the possibility that mnemonics (or settings) might be stored on the flash. :/ My original vision for Krux was that it would be a stateless device with a readonly filesystem that you could knowingly plug hardware pieces into to gain features like mnemonic storage or settings persistence. For example, a microSD for encrypted mnemonics or microSD for settings with mnemonics on dedicated USB-C devices like Yubikeys. This way, Krux can always choose the most hardcore "wipe" possible on shutdown, and you always know exactly where everything is stored. Hmm.

@tadeubas
Copy link
Contributor

Your "original vision for Krux" is still avaiable at releases under version 22.08.2. FOSS works that way, if someone want to change something, it changes accordingly to his view, if others like and share the same view, they change too on a specific repo. Lots of forks and versions could be available at the same time and the "original" and or "official" version or view could change with time.

Now regarding the feature, I don't see any problem to create a "restore defaults" and "full wipe" of what is connected to the device (flash/SDCard)

@odudex
Copy link
Member

odudex commented Sep 30, 2023

Discussion, criticism and questioning is healthy, and with planning and teamwork we can achieve higher gols than we would working independently. Also, something I learned with time is group achievements are far more satisfying than individual ones. Let's make an effort and to try to integrate our ideas and expectations the best possible way.
My take on this is that both settings and mnemonic storage are very convenient, and this convenience surpass the fact it wouldn't be stateless anymore. Actually, both modes are possible, so user is free to choose.
I think we can have separated "restore defaults" and "wipe device". When can use @jdlcdl method for a full wipe of each bit of the flash, keep the settings on ram, then save only the setting again on flash after the wipe, this way wiping only the encrypted mnemonics.

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Sep 30, 2023

Discussion, criticism and questioning is healthy
and
group achievements are far more satisfying than individual ones.

"Amen" to that! Each individual has blind-spots, in a team, they have fewer.

@ghost
Copy link

ghost commented Sep 30, 2023

Discussion, criticism and questioning is healthy, and with planning and teamwork we can achieve higher gols than we would working independently. Also, something I learned with time is group achievements are far more satisfying than individual ones. Let's make an effort and to try to integrate our ideas and expectations the best possible way.

Agreed. Particularly about planning and discussion! ;)

You don't want a project run by a group of people who agree on everything, otherwise it's an echo chamber subject to mob rule.

To be clear, I can see why the feature is liked, and I'm not saying it should be removed. However, such a big change to the project should have had more discussion around it, particularly all the possible edge cases that would come as a result of a core assumption being violated. I'll take the blame here that I should have been more active and diligent about this before it was added. All I can say is that I honestly didn't have the time to dedicate to this project the past year. Development continued without me or my input, so my only recourse is to retroactively voice my concerns now.

Incidentally, this is an issue I have with large PRs that are not focused on one feature: it's difficult to review them adequately or individually discuss each feature contained within, especially when the features intertwine and become dependent on one another. I don't like making someone throw out their hard work, so the result is I have to rubber stamp the whole thing. It's like an omnibus bill in congress. The outcome is suboptimal.

Going forward, I'm going to be more strict about forcing adherence to the guidelines for contributing here: https://github.com/selfcustody/krux#contributing

Anyway, the reason I don't like having a "Wipe Device" feature is that I think it should be the default behavior. And if we add it as an option, I think it will confuse a lot of users who will suddenly think, "Wait, all the times I used it before it wasn't wiping anything?" I'd rather we open a discussion about the different ways Krux is used, both as stateless and stateful, and see if we can more holistically design the UI around that.

In general, I think holistically reviewing the UI/UX of Krux every year or so would be useful to avoid adding too many options and menus and keep Krux simple and unsurprising (another core tenet). Established projects often run into the problem of local maxima optimization due to not taking a step back and thinking from first principles how they would design the project if given a blank slate.

@odudex
Copy link
Member

odudex commented Sep 30, 2023

So being objective, @jreesun, do you think the option to save settings and encrypted mnemonics on internal flash should be removed?
I don't demonize the use of internal flash, I even think it's safer to use than removable flash...

@qlrd
Copy link
Contributor

qlrd commented Sep 30, 2023

Anyway, the reason I don't like having a "Wipe Device" feature is that I think it should be the default behavior. And if we add it as an option, I think it will confuse a lot of users who will suddenly think, "Wait, all the times I used it before it wasn't wiping anything?" I'd rather we open a discussion about the different ways Krux is used, both as stateless and stateful, and see if we can more holistically design the UI around that.

In particular, I think we should remember what made us "Kruxers" (except @jreesun who is the "Adamic Kruxer"). I don't have the technical knowledge of krux codes (my contribution is more in the pedagogical aspect through tools with GUIs). Therefore, I will give my opinion as a user.

I fell in love with the project due to a paradigmatic exchange: the airgapped, amnesic and easily accessible tripartition (it would be possible to argue: "It has SeedSigner and SpecterDIY", but so, where I live, it is much easier to have a Sipeed and installing software than assembling the previous ones).

And from this I concluded that this was my demand. On the other hand, there are other demands, including from people who have experience with another paradigms (coldcard or trezor, for example) whose seed needs a security component.

Krux's initial proposal is that even this would not be necessary. However, everything that is created is transformed. And with Krux it would be no different; there were demands in Telegram groups for the most different implementations, including seed memorization, whose security component is encryption (I personally use and like it stateless).

So being objective, @jreesun, do you think the option to save settings and encrypted mnemonics on internal flash should be removed?

Would it be the case to have two firmware versions for each device? One stateless and one stateful? An initial setup on device that make it stateless or stateful? I don't know. But I think we should ask the majority of users whether this should remain the case or not (maybe by Cryptpad?).

@odudex
Copy link
Member

odudex commented Oct 2, 2023

@jdlcdl I believe I fixed the flash.erase() method on Maixpy with this, on the branch "sipeed_update" of our Maixpy.
I noticed a reboot would be necessary after a "wipe", as the SPIFFS would be re-formatted after reboot.
Since you dug into this, it would be helpful if you could take a look and help me find out if everything is working properly!

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Oct 2, 2023

Nice!

I recall that I had tested erasing 4096 as well as 65536 byte blocks, both with no luck. Since you have left the larger blocksize version unchanged will make for an effective test that your changes, and not something else in the sipeed update, is the solution. I will take a look again. thank you.

@odudex
Copy link
Member

odudex commented Oct 2, 2023

The changes affect both sizes(there's also a previous commit) , there was an error with the "if" too. Please check both!

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Oct 2, 2023

It's working, but actually, so is the larger block size erase, which I didn't expect to work at all since you didn't fix that in your latest commit.

I started by confirming that without your updated sipeed_update branch, flash.erase() still fails as it did in the past.

After booting firmware with your sipeed_update branch at c5127396:

  • flash.erase(0xd00000, 1024) returns quickly and does nothing because it shouldn't, it's an invalid blocksize for erase.
  • flash.erase(0xd00000, 4096) returns quickly and indeed sets those bytes to b'\xff', WOOHOO!!!
  • flash.erase(0xd00000, 65536) returns quickly and also sets those bytes to b'\xff', which I didn't expect because it appears you didn't change the elif (int_size==65536) branch.

I wonder if simply updating sipeed's MaixPy solves this without your changes. I do recall seeing some w25qxx changes, but I'll admit that I didn't understand their significance.

I did a git reset --hard 3ff9b4a to remove your very latest change, leaving your first edit (fix flash erase method).
Trying to flash.erase an invalid block size didn't produce an error, but it also didnt do anything, as expected. flash.erase() of a 4096 or 65536 blocksize worked just as above... indicating that your latest change didn't hurt anything but might not have been a required fix.

I did a git reset --hard 18e1b58 to get back to your original sipeed_update branch (removed unused IDE related code) without either of your latest flash-erase fixes. And back at square one with flash.erase() that appears to be broken.

Summary:
I confirm that it was commit 3ff9b4a which was the fix that made the difference, at least on my amigo. Your latest commit doesn't break anything, but maybe is not needed? Does this mean that it was about a type error between a micropython object and a 4byte c integer? Anyways, great wizardry! Bravo!

Also, I confirm that a reboot is needed to setup spiffs again.


My final test session from the usb console below after booting only your first fix w/ MaixPy at 3ff9b4a:

KeyboardInterrupt: 
MicroPython v1.11 on 2023-10-02; Sipeed_M1 with kendryte-k210
Type "help()" for more information.
>>> 
>>> from machine import WDT;WDT().stop()
>>> from Maix import utils # needed for my all_bytes_are() function
>>> 
paste mode; Ctrl-C to cancel, Ctrl-D to finish
=== def all_bytes_are(byte, begin, length, block_size=2**12, verbose=False):
===     '''
===     returns True if all bytes in flash are the same as byte, otherwise False
=== 
===     assumes that utils.flash_read() behaves as if imported from Maix
===   ==         print("\nthe %s bytes at %s are %s 0x%s." % (
===             length, hex(begin), 'ALL' if answer else 'NOT all', hexlify(byte).decode()))
=== 
===     return answer
=== 
>>> spiffs, xff, invalid_bs, small_bs, big_bs = 0xd00000, b'\xff', 1234, 4096, 65536
>>> all_bytes_are(xff, spiffs, invalid_bs)
False
>>> import flash
>>> flash.erase(spiffs, invalid_bs)
>>> all_bytes_are(xff, spiffs, invalid_bs)
False
>>> all_bytes_are(xff, spiffs, small_bs)
False
>>> flash.erase(spiffs, small_bs)
>>> all_bytes_are(xff, spiffs, small_bs)
True
>>> all_bytes_are(xff, spiffs, big_bs)
False
>>> flash.erase(spiffs, big_bs)
>>> all_bytes_are(xff, spiffs, big_bs)
True

and my all_bytes_are() function, which I cut/paste with ctrl-E and ctrl-D:

def all_bytes_are(byte, begin, length, block_size=2**12, verbose=False):
    '''
    returns True if all bytes in flash are the same as byte, otherwise False

    assumes that utils.flash_read() behaves as if imported from Maix
    '''

    from binascii import hexlify

    answer = True

    if verbose:
        print("Checking if %s bytes of flash at %s are all 0x%s..." % (
            length, hex(begin), hexlify(byte).decode()), end='')

    bytes_read = 0
    while bytes_read < length:
        if bytes_read + block_size < length:
            if utils.flash_read(begin+bytes_read, block_size) != byte * block_size:
                answer = False
                break
            bytes_read += block_size
        else:
            if utils.flash_read(begin+bytes_read, length-bytes_read) != byte * (length - bytes_read):
                answer = False
                break
            bytes_read += length - bytes_read

        if verbose:
            print('.', end='')

    if verbose:
        print("\nthe %s bytes at %s are %s 0x%s." % (
            length, hex(begin), 'ALL' if answer else 'NOT all', hexlify(byte).decode()))

    return answer

@odudex
Copy link
Member

odudex commented Oct 2, 2023

Maybe part of the last commit is unnecessary, but some is. If you will read a block right after erasing it, and don't wait for the w25qxx_is_busy_dma() the result might be wrong. The bigger error was comparing two different variable types on the if that checked the block size

@odudex
Copy link
Member

odudex commented Oct 3, 2023

def erase_spiffs():
    '''
    writes 0xff to entire SPIFFS because it appears flash.erase() doesn't work on Amigo TFT

    assumes `import flash` origin is selfcustody/MaixPy/components/micropython/port/src/flash
    '''

    FLASH_SIZE = 2**24
    SPIFFS_ADDR = 0xd00000
    BLOCK_SIZE = 0x10000

    import flash

    empty_buf = b'\xff' * BLOCK_SIZE
    for address in range(SPIFFS_ADDR, FLASH_SIZE, BLOCK_SIZE):
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            continue

        flash.erase(address, BLOCK_SIZE)
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            print('%d bytes at %s erased' % (BLOCK_SIZE, hex(address)))
            continue
 
        flash.write(address, empty_buf)
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            print('%d bytes at %s written w/ 0xff' % (BLOCK_SIZE, hex(address)))
            continue

Besides it is working I was still thinking on optimizations for this, and using your method from first post I noticed something: The if statement below never is True, and it always erase all blocks.
if flash.read(address, BLOCK_SIZE) == empty_buf:
Maybe the data is too large to be compared on a single if statement?
On Maixpy C method to write the flash they check byte per byte in a for loop, if a byte needs to be erased then they break the test loop and erase the hole block.

@odudex
Copy link
Member

odudex commented Oct 3, 2023

Also, do you agree this verification could be added directly on the Maixpy erase method (in C), as it probably would be faster than doing it within the python application?

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Oct 3, 2023

As for the comparison never being true, I have not witnessed that in the past but I only ran this function a few times originally, then twice yesterday. What I was intending for this code to do was to print nothing when a block is already empty (all b'\xff'), else "erased" if the flash.erase actually worked, or "written" if it had to write the bytes the slow-ish way. If you figure out how to reproduce all bytes as b'\xff' and this still doesn't work, I'd love to be able to do it myself. Does it work with smaller block sizes? I do get memory allocation errors often for block sizes 128k and above, gc.collect() helps sometimes. I've never had the same issue with 64k blocks which gc.collect() didn't resolve.

I would hope to figure out if flash.erase() is working religiously and then just assume that it will work each time. What I've read of these SPI flash devices is that they're fast and reading and writing, but when it comes to the special erase functions, they're especially efficient (as long as there is a particular function for efficient erase, like "the whole chip" or "4k" or "64k" at a time). I'd hate to start double-guessing and slowly verifying otherwise efficient operations that should just work as expected.

If we end up having to verify, I'd agree that in C it should be much faster, I hope it's not necessary with your latest changes.

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Oct 3, 2023

I've added another print(), when nothing is done, to the erase_spiffs() function after @odudex mentioned that every other loop does nothing. I suspect it's because a mostly empty spiffs space will only have format markers every 128k.

def erase_spiffs():
    '''
    writes 0xff to entire SPIFFS because it appears flash.erase() doesn't work on Amigo TFT

    assumes `import flash` origin is selfcustody/MaixPy/components/micropython/port/src/flash
    '''

    FLASH_SIZE = 2**24
    SPIFFS_ADDR = 0xd00000
    BLOCK_SIZE = 0x10000

    import flash

    empty_buf = b'\xff' * BLOCK_SIZE
    for address in range(SPIFFS_ADDR, FLASH_SIZE, BLOCK_SIZE):
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            print('%d bytes at %s already empty' % (BLOCK_SIZE, hex(address)))
            continue

        flash.erase(address, BLOCK_SIZE)
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            print('%d bytes at %s erased' % (BLOCK_SIZE, hex(address)))
            continue
 
        flash.write(address, empty_buf)
        if flash.read(address, BLOCK_SIZE) == empty_buf:
            print('%d bytes at %s written w/ 0xff' % (BLOCK_SIZE, hex(address)))
            continue

Using the current MaixPy w/ broken flash.erase(), I get...

erase_spiffs()
65536 bytes at 0xd00000 written w/ 0xff
65536 bytes at 0xd10000 written w/ 0xff
65536 bytes at 0xd20000 written w/ 0xff
65536 bytes at 0xd30000 already empty
65536 bytes at 0xd40000 written w/ 0xff
65536 bytes at 0xd50000 already empty
65536 bytes at 0xd60000 written w/ 0xff
65536 bytes at 0xd70000 already empty
65536 bytes at 0xd80000 written w/ 0xff
65536 bytes at 0xd90000 already empty
65536 bytes at 0xda0000 written w/ 0xff
65536 bytes at 0xdb0000 already empty
65536 bytes at 0xdc0000 written w/ 0xff
65536 bytes at 0xdd0000 already empty
65536 bytes at 0xde0000 written w/ 0xff
65536 bytes at 0xdf0000 already empty
65536 bytes at 0xe00000 written w/ 0xff
65536 bytes at 0xe10000 already empty
65536 bytes at 0xe20000 written w/ 0xff
65536 bytes at 0xe30000 already empty
65536 bytes at 0xe40000 written w/ 0xff
65536 bytes at 0xe50000 already empty
65536 bytes at 0xe60000 written w/ 0xff
65536 bytes at 0xe70000 already empty
65536 bytes at 0xe80000 written w/ 0xff
65536 bytes at 0xe90000 already empty
65536 bytes at 0xea0000 written w/ 0xff
65536 bytes at 0xeb0000 already empty
65536 bytes at 0xec0000 written w/ 0xff
65536 bytes at 0xed0000 already empty
65536 bytes at 0xee0000 written w/ 0xff
65536 bytes at 0xef0000 already empty
65536 bytes at 0xf00000 written w/ 0xff
65536 bytes at 0xf10000 already empty
65536 bytes at 0xf20000 written w/ 0xff
65536 bytes at 0xf30000 already empty
65536 bytes at 0xf40000 written w/ 0xff
65536 bytes at 0xf50000 already empty
65536 bytes at 0xf60000 written w/ 0xff
65536 bytes at 0xf70000 already empty
65536 bytes at 0xf80000 written w/ 0xff
65536 bytes at 0xf90000 already empty
65536 bytes at 0xfa0000 written w/ 0xff
65536 bytes at 0xfb0000 already empty
65536 bytes at 0xfc0000 written w/ 0xff
65536 bytes at 0xfd0000 already empty
65536 bytes at 0xfe0000 written w/ 0xff
65536 bytes at 0xff0000 already empty
>>> 

After booting odudex's MaixPy sipeed_update branch at c512739, I get:

>>> erase_spiffs()
65536 bytes at 0xd00000 erased
65536 bytes at 0xd10000 already empty
65536 bytes at 0xd20000 erased
65536 bytes at 0xd30000 already empty
65536 bytes at 0xd40000 erased
65536 bytes at 0xd50000 already empty
65536 bytes at 0xd60000 erased
65536 bytes at 0xd70000 already empty
65536 bytes at 0xd80000 erased
65536 bytes at 0xd90000 already empty
65536 bytes at 0xda0000 erased
65536 bytes at 0xdb0000 already empty
65536 bytes at 0xdc0000 erased
65536 bytes at 0xdd0000 already empty
65536 bytes at 0xde0000 erased
65536 bytes at 0xdf0000 already empty
65536 bytes at 0xe00000 erased
65536 bytes at 0xe10000 already empty
65536 bytes at 0xe20000 erased
65536 bytes at 0xe30000 already empty
65536 bytes at 0xe40000 erased
65536 bytes at 0xe50000 already empty
65536 bytes at 0xe60000 erased
65536 bytes at 0xe70000 already empty
65536 bytes at 0xe80000 erased
65536 bytes at 0xe90000 already empty
65536 bytes at 0xea0000 erased
65536 bytes at 0xeb0000 already empty
65536 bytes at 0xec0000 erased
65536 bytes at 0xed0000 already empty
65536 bytes at 0xee0000 erased
65536 bytes at 0xef0000 already empty
65536 bytes at 0xf00000 erased
65536 bytes at 0xf10000 already empty
65536 bytes at 0xf20000 erased
65536 bytes at 0xf30000 already empty
65536 bytes at 0xf40000 erased
65536 bytes at 0xf50000 already empty
65536 bytes at 0xf60000 erased
65536 bytes at 0xf70000 already empty
65536 bytes at 0xf80000 erased
65536 bytes at 0xf90000 already empty
65536 bytes at 0xfa0000 erased
65536 bytes at 0xfb0000 already empty
65536 bytes at 0xfc0000 erased
65536 bytes at 0xfd0000 already empty
65536 bytes at 0xfe0000 erased
65536 bytes at 0xff0000 already empty
>>>

If we look at the bytes of spiffs (I have a hacky hexdump), before being erased, they look like:

>>> hd=HexDumpSPIFlash(0xd00000, width=32, lines=50)
>>> hd.run()
d00000  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  01 80 01 00  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d00020  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 124 squeezed
d00fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d00fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  31 15 00 00  |............................1...|
d01000  01 80 00 00  7e 00 00 00  2f 00 00 00  01 2f 73 65  74 74 69 6e  67 73 2e 6a  73 6f 6e 00  00 00 00 00  |....~.../..../settings.json.....|
d01020  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
... 1 squeezed
d01060  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
d01080  00 00 00 00  00 00 00 00  00 00 00 00  00 00 02 00  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d010a0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 121 squeezed
d01fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d02000  01 00 00 00  7e 7b 22 73  65 74 74 69  6e 67 73 22  3a 20 7b 22  61 70 70 65  61 72 61 6e  63 65 22 3a  |....~{"settings": {"appearance":|
d02020  20 7b 22 74  68 65 6d 65  22 3a 20 22  44 61 72 6b  22 7d 7d 7d  ff ff ff ff  ff ff ff ff  ff ff ff ff  | {"theme": "Dark"}}}............|
d02040  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 124 squeezed
d02fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d03000  01 80 00 00  7e 00 00 00  51 00 00 00  01 2f 73 65  74 74 69 6e  67 73 2e 6a  73 6f 6e 00  00 00 00 00  |....~...Q..../settings.json.....|
d03020  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
... 1 squeezed
d03060  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
d03080  00 00 00 00  00 00 00 00  00 00 00 00  00 00 04 00  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d030a0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 121 squeezed
d03fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d04000  01 00 00 00  7e 7b 22 73  65 74 74 69  6e 67 73 22  3a 20 7b 22  74 6f 75 63  68 73 63 72  65 65 6e 22  |....~{"settings": {"touchscreen"|
d04020  3a 20 7b 22  74 68 72 65  73 68 6f 6c  64 22 3a 20  32 32 7d 2c  20 22 61 70  70 65 61 72  61 6e 63 65  |: {"threshold": 22}, "appearance|
d04040  22 3a 20 7b  22 74 68 65  6d 65 22 3a  20 22 44 61  72 6b 22 7d  7d 7d ff ff  ff ff ff ff  ff ff ff ff  |": {"theme": "Dark"}}}..........|
d04060  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 123 squeezed
d04fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d05000  01 80 00 00  7e 00 00 00  6e 00 00 00  01 2f 73 65  74 74 69 6e  67 73 2e 6a  73 6f 6e 00  00 00 00 00  |....~...n..../settings.json.....|
d05020  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
... 1 squeezed
d05060  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
d05080  00 00 00 00  00 00 00 00  00 00 00 00  00 00 06 00  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d050a0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 121 squeezed
d05fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d06000  01 00 00 00  7e 7b 22 73  65 74 74 69  6e 67 73 22  3a 20 7b 22  74 6f 75 63  68 73 63 72  65 65 6e 22  |....~{"settings": {"touchscreen"|
d06020  3a 20 7b 22  74 68 72 65  73 68 6f 6c  64 22 3a 20  32 32 7d 2c  20 22 69 31  38 6e 22 3a  20 7b 22 6c  |: {"threshold": 22}, "i18n": {"l|
d06040  6f 63 61 6c  65 22 3a 20  22 65 6e 2d  55 53 22 7d  2c 20 22 61  70 70 65 61  72 61 6e 63  65 22 3a 20  |ocale": "en-US"}, "appearance": |
d06060  7b 22 74 68  65 6d 65 22  3a 20 22 44  61 72 6b 22  7d 7d 7d ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |{"theme": "Dark"}}}.............|
d06080  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 122 squeezed
d06fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d07000  01 80 00 00  7e 00 00 00  8c 00 00 00  01 2f 73 65  74 74 69 6e  67 73 2e 6a  73 6f 6e 00  00 00 00 00  |....~......../settings.json.....|
d07020  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
... 1 squeezed
d07060  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
d07080  00 00 00 00  00 00 00 00  00 00 00 00  00 00 08 00  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d070a0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 121 squeezed
d07fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d08000  01 00 00 00  7e 7b 22 73  65 74 74 69  6e 67 73 22  3a 20 7b 22  74 6f 75 63  68 73 63 72  65 65 6e 22  |....~{"settings": {"touchscreen"|
d08020  3a 20 7b 22  74 68 72 65  73 68 6f 6c  64 22 3a 20  32 32 7d 2c  20 22 69 31  38 6e 22 3a  20 7b 22 6c  |: {"threshold": 22}, "i18n": {"l|
d08040  6f 63 61 6c  65 22 3a 20  22 65 6e 2d  55 53 22 7d  2c 20 22 6c  6f 67 67 69  6e 67 22 3a  20 7b 22 6c  |ocale": "en-US"}, "logging": {"l|
d08060  65 76 65 6c  22 3a 20 22  4e 4f 4e 45  22 7d 2c 20  22 61 70 70  65 61 72 61  6e 63 65 22  3a 20 7b 22  |evel": "NONE"}, "appearance": {"|
d08080  74 68 65 6d  65 22 3a 20  22 44 61 72  6b 22 7d 7d  7d ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |theme": "Dark"}}}...............|
d080a0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 121 squeezed
d08fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d09000  01 80 00 00  f8 00 00 00  ac 00 00 00  01 2f 73 65  74 74 69 6e  67 73 2e 6a  73 6f 6e 00  00 00 00 00  |............./settings.json.....|
d09020  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
... 1 squeezed
d09060  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  |................................|
d09080  00 00 00 00  00 00 00 00  00 00 00 00  00 00 0a 00  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d090a0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 121 squeezed
d09fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d0a000  01 00 00 00  fc 7b 22 73  65 74 74 69  6e 67 73 22  3a 20 7b 22  62 69 74 63  6f 69 6e 22  3a 20 7b 22  |.....{"settings": {"bitcoin": {"|
d0a020  6e 65 74 77  6f 72 6b 22  3a 20 22 6d  61 69 6e 22  7d 2c 20 22  74 6f 75 63  68 73 63 72  65 65 6e 22  |network": "main"}, "touchscreen"|
d0a040  3a 20 7b 22  74 68 72 65  73 68 6f 6c  64 22 3a 20  32 32 7d 2c  20 22 69 31  38 6e 22 3a  20 7b 22 6c  |: {"threshold": 22}, "i18n": {"l|
d0a060  6f 63 61 6c  65 22 3a 20  22 65 6e 2d  55 53 22 7d  2c 20 22 6c  6f 67 67 69  6e 67 22 3a  20 7b 22 6c  |ocale": "en-US"}, "logging": {"l|
d0a080  65 76 65 6c  22 3a 20 22  4e 4f 4e 45  22 7d 2c 20  22 61 70 70  65 61 72 61  6e 63 65 22  3a 20 7b 22  |evel": "NONE"}, "appearance": {"|
d0a0a0  74 68 65 6d  65 22 3a 20  22 44 61 72  6b 22 7d 7d  7d ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |theme": "Dark"}}}...............|
d0a0c0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 2935 squeezed
d20fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d20fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  3e 15 00 00  |............................>...|
d21000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
d40fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d40fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  3f 15 00 00  |............................?...|
d41000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
d60fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d60fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  3c 15 00 00  |............................<...|
d61000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
d80fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
d80fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  3d 15 00 00  |............................=...|
d81000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
da0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
da0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  3a 15 00 00  |............................:...|
da1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
dc0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
dc0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  3b 15 00 00  |............................;...|
dc1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
dc1020  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4092 squeezed
de0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
de0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  38 15 00 00  |............................8...|
de1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
e00fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
e00fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  39 15 00 00  |............................9...|
e01000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
e20fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
e20fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  26 15 00 00  |............................&...|
e21000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
e40fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
e40fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  27 15 00 00  |............................'...|
e41000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
e60fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
e60fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  24 15 00 00  |............................$...|
e61000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
e80fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
e80fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  25 15 00 00  |............................%...|
e81000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
ea0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
ea0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  22 15 00 00  |............................"...|
ea1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
ec0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
ec0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  23 15 00 00  |............................#...|
ec1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
ee0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
ee0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  20 15 00 00  |............................ ...|
ee1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
f00fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
f00fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  21 15 00 00  |............................!...|
f01000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
f20fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
f20fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  2e 15 00 00  |................................|
f21000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
f40fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
f40fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  2f 15 00 00  |............................/...|
f41000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
f60fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
f60fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  2c 15 00 00  |............................,...|
f61000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
f80fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
f80fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  2d 15 00 00  |............................-...|
f81000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
fa0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
fa0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  2a 15 00 00  |............................*...|
fa1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
fc0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
fc0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  2b 15 00 00  |............................+...|
fc1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 4093 squeezed
fe0fc0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
fe0fe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  28 15 00 00  |............................(...|
fe1000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 3966 squeezed
ffffe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|

...then it wraps around.

After being erased, same would show all empty:

>>> hd=HexDumpSPIFlash(0xd00000, width=32, lines=50)
>>> hd.run()
d00000  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|
... 98302 squeezed
ffffe0  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  ff ff ff ff  |................................|

My hexdump tool for use at the console:

class HexDumpSPIFlash:
    '''
    hex dump for maixpy 16MB SPI Flash

    assumes that utils.flash_read() behaves as if imported from Maix
    '''

    size = 2**24
    def __init__(self, begin=0x0, width=16, lines=16, squeeze=True):
        self.cursor = begin
        self.configure(width=width, lines=lines, squeeze=squeeze)

    def next(self):
        pass

    def prev(self):
        page_size = self.width * self.lines
        self.cursor = (self.size + self.cursor - (page_size * 2)) % self.size

    def seek(self, address):
        self.cursor = address % self.size

    def configure(self, width=None, lines=None, squeeze=True):
        if type(width) == int and width > 0: 
            self.width = width

        if type(lines) == int and lines > 0: 
            self.lines = lines

        if type(squeeze) == bool:
            self.squeeze = squeeze

        byte_format = '  '.join([' '.join(['{:02x}']*4)]*(self.width//4))
        if self.width % 4:
             byte_format = '  '.join([byte_format, ' '.join(['{:02x}']*(self.width%4))])
        self.fmt = '{}  {}  |{}|'.format('{:06x}', byte_format, '{:.1s}'*self.width)

    def read(self, update_cursor=False):
        def format_record(address, record):
            if len(record) == self.width:
                return self.fmt.format(
                    *[address]
                    +[x for x in record]
                    +[len(repr(str(chr(x))))==3 and str(chr(x)) or '.' for x in record]
                )
            else:
                return '{:06x}  {} [EOR]'.format(
                    address, 
                    ' '.join(['{:02x}'.format(x) for x in record])
                    )
        first = self.cursor
        answer, buf, i_buf, line_no, repeats, last_record = [], (None, b''), 0, 0, 0, (None, b'')
        while line_no < self.lines:
            if i_buf + 1 >= len(buf[1]):
                buf = (first, utils.flash_read(first, self.width*self.lines))
                i_buf = 0
 
            record = (first, buf[1][i_buf:i_buf+self.width])
            if repeats:
                if record[1] != last_record[1]:
                    answer.extend([
                        '... {:d} squeezed'.format(repeats-1) if repeats>1 else '', 
                        format_record(*last_record), 
                        format_record(*record)
                    ]) 
                    repeats = 0
                    line_no += 3
                else:
                    repeats += 1
            else:
                if self.squeeze and record[1] == last_record[1]:
                    repeats += 1
                else:
                    answer.append(format_record(*record))
                    line_no += 1
            i_buf += len(record[1])
            first = (first + len(record[1])) % self.size
            last_record = record
        if repeats:
            answer.extend([
                '... {:d} squeezed'.format(repeats-1) if repeats>1 else '', 
                format_record(*record)
            ]) 
        if update_cursor:
            self.cursor = first
        return '\n'.join(answer)

    def run(self):
        def set_lines():
            self.configure(lines=int(input('Enter number of lines: ')))

        def set_width():
            self.configure(width=int(input('Enter number of bytes per line: ')))

        def toggle_squeeze():
            self.configure(squeeze=not self.squeeze)

        def seek():
            address = input('Enter an address: ')
            if address[:2] == '0b':
                 address = int(address[2:], 2)
            elif address[:2] == '0x':
                 address = int(address[2:], 16)
            else:
                 address = int(address)
            self.seek(address)

        repl = {
        'j': self.next,
        'k': self.prev,
        'l': set_lines,
        'w': set_width,
        's': toggle_squeeze,
        '/': seek,
        }
        print(self.read(update_cursor=True))
        while True:
            _in = input('\b')
            if _in and _in[0] in repl:
                repl[_in]()
                print(self.read(update_cursor=True))
            elif _in == 'q':
                return
            else: print('Try one of %s or "q" to quit.' % [x for x in repl.keys()])

@odudex
Copy link
Member

odudex commented Oct 3, 2023

Wow, I've been learning a lot with your tools and methods, thank you!
I believe we can use your findings to help demystify the flash and improve the code to use it in a safe and transparent way.

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Oct 3, 2023

Thank you for that last comment. It makes my exploration into flash feel worth while.

I'll admit, my original thoughts when I received my amigo gift were "Wow, 16MB of internal flash? What's that work-out to be, like room for 500k private keys? How can we inspect that and be sure?"

I have more hacky scripts, hopefully they'll evolve into something that others can use easily.

@jdlcdl
Copy link
Collaborator Author

jdlcdl commented Oct 4, 2023

Remembering that I'd confirmed a reboot works to setup spiffs space after it's been erased w/ flash.erase(). As well, we can use os.flash_format() as an alternative w/o reboot, so it's ready to receive whatever files need to be re-saved.

>>> import os
>>> os.flash_format()
[MAIXPY]:Spiffs Unmount.
[MAIXPY]:Spiffs Formating...
[MAIXPY]:Spiffs Format successful 
[MAIXPY]:Spiffs Mount successful 
True
>>> os.getcwd()
'/'
>>> os.listdir()
['flash']
>>>

@tadeubas
Copy link
Contributor

tadeubas commented Mar 14, 2024

Can we consider this done in release 24.03.0 ?

@odudex
Copy link
Member

odudex commented Mar 14, 2024

Yes, @jdlcdl if there's something else please let us know and we can re-open it.

@odudex odudex closed this as completed Mar 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants