Skip to content

Instantly share code, notes, and snippets.

@jdlcdl
Last active August 1, 2023 17:08
Show Gist options
  • Save jdlcdl/a01dbf21771516581b4ccfda49622293 to your computer and use it in GitHub Desktop.
Save jdlcdl/a01dbf21771516581b4ccfda49622293 to your computer and use it in GitHub Desktop.
sipeed maix amigo tft: flash exploration

Sipeed Maix Amigo TFT w/ 16MB flash.

Using ktool.py from a docker build of krux ./firmware/Kboot/build/ktool.py to access flash on amigo.

See the loboris Kboot readme


Erasing the flash, then reading and inspecting:

~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -E
...
[INFO] SPI Flash erased. 

The amigo no longer shows any sign of life on its screen, but it's not quite a "brick". When pressing the power-button for 6 seconds, I assume it shuts down, because another 1 second press then blinks the white-LED as if powered on again, but it never appears to boot.

Can connect to serial console via /dev/ttyUSB1. After hitting the soft-reset button on amigo, output is:

interesting, something's wrong, boot failed with exit code 233, go to find your vendor.

We'll read from flash and save a flash_dump in /tmp:

~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x0 -L 16777216 /tmp/k210.flash_dump
...
[INFO] Read 16777216 Bytes from Flash address 0x0 
Read Flash: |=============================================| 100.0% 11kiB/s
[INFO] Read 16777216 Bytes in 1470.181s

What does "SPI Flash erased" really look like?

~/krux$ du -b /tmp/k210.flash_dump
16777216	/tmp/k210.flash_dump

~/krux$ hd /tmp/k210.flash_dump
00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
01000000

It looks like an erased flash, from beginning to end, is nothing but 1s


Flashing a recent version of krux firmware (23.07.beta14) to the amigo:

~/krux$ git log | head -3
commit 1752f422a136479879285cad47238db07307494e
Author: Odudex <eduardo.schoenknecht@gmail.com>
Date:   Tue Jul 4 17:10:44 2023 -0300

note: a previous version of this doc listed the wrong commit that was "head" while building binaries.

~/krux$ du -b ./build/*
1912576	./build/firmware.bin
936687	./build/kboot.kfpkg
~/krux$ sha256sum ./build/*
f234f6b1b517af8b988980c395f482cf535d07c67ef978ca3cc175bd07aa54e8  ./build/firmware.bin
2a1c5c7a19e12cc6d7db0d11febf68237a891f09ae5dbb347dcb538e40be7417  ./build/kboot.kfpkg

Note: the smaller kboot.kfpkg is a compressed archive, we'll refer to its contents later.

~/krux$ unzip -l ./build/kboot.kfpkg
Archive:  ./build/kboot.kfpkg
  Length      Date    Time    Name
---------  ---------- -----   ----
      647  2023-07-04 18:12   flash-list.json
      608  2023-07-04 18:12   bootloader_lo.bin
     8112  2023-07-04 18:12   bootloader_hi.bin
     4096  2023-06-13 10:01   config.bin
  1912576  2023-07-04 18:12   firmware.bin
---------                     -------
  1926039                     5 files
~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE ./build/kboot.kfpkg 
...
[INFO] Extracting KFPKG ...  
[INFO] Writing bootloader_lo.bin to Flash address 0x00000000 
[INFO] Flashing firmware block with SHA suffix 
Programming BIN: |=============================================| 100.0% 
[INFO] Flashed 645 B [1 chunks of 4096B] (00000000~00000FFF) in 0.420s 
[INFO] Writing bootloader_hi.bin to Flash address 0x00001000 
[INFO] Flashing firmware block with SHA suffix 
Programming BIN: |=============================================| 100.0% 
[INFO] Flashed 8149 B [2 chunks of 4096B] (00001000~00002FFF) in 0.848s 
[INFO] Writing config.bin to Flash address 0x00004000 
Programming DATA: |=============================================| 100.0% 
[INFO] Flashed 4096 B [1 chunks of 4096B] (00004000~00004FFF) in 0.414s 
[INFO] Writing config.bin to Flash address 0x00005000 
Programming DATA: |=============================================| 100.0% 
[INFO] Flashed 4096 B [1 chunks of 4096B] (00005000~00005FFF) in 0.411s 
[INFO] Writing firmware.bin to Flash address 0x00080000 
[INFO] Flashing firmware block with SHA suffix 
Programming BIN: |=============================================| 100.0% 10kiB/s
[INFO] Flashed 1912613 B [30 chunks of 65536B] (00080000~0025FFFF) in 183.011s 
[INFO] Rebooting...

The amigo boots krux as expected.


Reading the six k210 4K-aligned sectors from flash, saving, inspecting:

This time we'll read individual flash sectors w/ krux freshly installed, booted once, unused.

Commands to do so are below, w/o any of their output:

~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x0 -L 4096 /tmp/k210.0_Kboot
~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x1000 -L 12288 /tmp/k210.1_Kboot
~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x4000 -L 4096 /tmp/k210.2_main
~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x5000 -L 4096 /tmp/k210.3_backup
~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x6000 -L 40960 /tmp/k210.4_reserved
~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x10000 -L 16711680 /tmp/k210.5_app_user

The flash file-dump sizes are as expected. Individual sector dumps sum to the same size as the full flash dump.

~/krux$ du -b /tmp/k210.*
4096	/tmp/k210.0_Kboot
12288	/tmp/k210.1_Kboot
4096	/tmp/k210.2_main
4096	/tmp/k210.3_backup
40960	/tmp/k210.4_reserved
16711680	/tmp/k210.5_app_user
16777216	/tmp/k210.flash_dump

~/krux$ python3 -c 'print(4096+12288+4096+4096+40960+16711680)'
16777216

These might be useful to know if something that shouldn't change, thru usage, does change later.

~/krux$ sha256sum /tmp/k210.[0-5]*
33ea1b4fdd86330dfac92f5147451c5315d308517e1e8a6b9d3c89d437b9721a  /tmp/k210.0_Kboot
c4d1e8bae5f524d759995820ce487612c20d9ad7a13ccdf35fdde8b9900a3d51  /tmp/k210.1_Kboot
c9b9c4adcabe9c353a907f44c101c3b29756b30762e4b282d87d2a92400e2198  /tmp/k210.2_main
c9b9c4adcabe9c353a907f44c101c3b29756b30762e4b282d87d2a92400e2198  /tmp/k210.3_backup
5acd2700bacfed75f41c8f0df2dd7e9f11b3a214677ce2ef525a369c151e60da  /tmp/k210.4_reserved
35b3972c990f90870e17681e6b5a4c8ca7ca57e88d879abb05f677f9c69c8837  /tmp/k210.5_app_user

A deeper inspection of these flash sectors:

/tmp/k210.0_Kboot

This is Kboot app stage0, 4096 bytes starting at 0x0 in flash

This contains bootloader_lo.bin from ./build/kboot.kfpkg.

~/krux$ unzip -p ./build/kboot.kfpkg bootloader_lo.bin | hd | head -3
00000000  6f 00 40 00 f3 27 40 f1  81 27 17 07 00 00 23 27  |o.@..'@..'....#'|
00000010  f7 20 95 cb 97 07 00 00  93 87 07 20 98 43 85 46  |. ......... .C.F|
00000020  17 06 00 00 23 28 d6 1e  01 e7 01 00 98 43 75 df  |....#(.......Cu.|

~/krux$ hd /tmp/k210.0_Kboot | head -3
00000000  00 60 02 00 00 6f 00 40  00 f3 27 40 f1 81 27 17  |.`...o.@..'@..'.|
00000010  07 00 00 23 27 f7 20 95  cb 97 07 00 00 93 87 07  |...#'. .........|
00000020  20 98 43 85 46 17 06 00  00 23 28 d6 1e 01 e7 01  | .C.F....#(.....|

It looks like bootloader_lo.bin in flash, with an offset because of a 5 byte header.

See https://github.com/loboris/ktool/blob/0345aa90d9b3830641373fb4e3ce4edf45d0a46f/ktool.py#L1161 Format is header + content + footer where header=AES byte + 4byte little endian size of content, and footer is sha256(header+content). ie: 0x00000260=608

Previously, we saw that bootloader_lo.bin is only 608 bytes while the flash dump sector is 4096. Let's be sure that bootloader_lo.bin is really there.

~/krux$ unzip -p ./build/kboot.kfpkg bootloader_lo.bin | sha256sum
2e050a92efdcb172cb5c6f7cb0b669ba654d2d20219f810fd9fc543f7ef05c3e  -

~/krux$ tail -c +6 /tmp/k210.0_Kboot | head -c 608 | sha256sum
2e050a92efdcb172cb5c6f7cb0b669ba654d2d20219f810fd9fc543f7ef05c3e  -

The rest of this flash sector is the 32 byte sha256 hash of all 613 bytes -- including the 5 byte header, followed by 0x00 bytes to fill the sector.

~/krux$ head -c 613 /tmp/k210.0_Kboot | sha256sum
4d1c6cf53b7b2fea3ea86c327753c745636b3b3db33473cf63faa30b443a3cb6  -

~/krux$ tail -c +614 /tmp/k210.0_Kboot | hd
00000000  4d 1c 6c f5 3b 7b 2f ea  3e a8 6c 32 77 53 c7 45  |M.l.;{/.>.l2wS.E|
00000010  63 6b 3b 3d b3 34 73 cf  63 fa a3 0b 44 3a 3c b6  |ck;=.4s.c...D:<.|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000d90  00 00 00 00 00 00 00 00  00 00 00                 |...........|
00000d9b

/tmp/k210.1_Kboot

This is Kboot app stage1, 12288 bytes starting at 0x1000 in flash

This contains bootloader_hi.bin from ./build/kboot.kfpkg w/ a 5 byte header and 32 byte hash footer (described above), followed by some 0x00 bytes to fill a 4096 block and an untouched 4096 block of 0xff bytes from our flash erase.

~/krux$ head -c 5 /tmp/k210.1_Kboot | hd
00000000  00 b0 1f 00 00                                    |.....|
00000005

~/krux$ python3 -c 'print(0x00001fb0)'
8112

~/krux$ tail -c +6 /tmp/k210.1_Kboot | head -c 8112 | sha256sum
f005f7c8b13aa2719cad58492a8432f14b68b2f845b58e5b5e81ed6309be6172  -

~/krux$ unzip -p ./build/kboot.kfpkg bootloader_hi.bin | sha256sum
f005f7c8b13aa2719cad58492a8432f14b68b2f845b58e5b5e81ed6309be6172  -

~/krux$ head -c 8117 /tmp/k210.1_Kboot | sha256sum
a0b2872077b62b18ac88e4f42ea74b6e26146e8b3d70811d5f56cc0c1f77c753  -

~/krux$ tail -c +8118 /tmp/k210.1_Kboot | hd
00000000  a0 b2 87 20 77 b6 2b 18  ac 88 e4 f4 2e a7 4b 6e  |... w.+.......Kn|
00000010  26 14 6e 8b 3d 70 81 1d  5f 56 cc 0c 1f 77 c7 53  |&.n.=p.._V...w.S|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000040  00 00 00 00 00 00 00 00  00 00 00 ff ff ff ff ff  |................|
00000050  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00001040  ff ff ff ff ff ff ff ff  ff ff ff                 |...........|
0000104b

/tmp/k210.2_main

This is main boot configuration, 4096 bytes starting at 0x4000 in flash

~/krux$ hd /tmp/k210.2_main
00000000  5a a5 d0 c5 00 08 00 00  00 1a a0 00 24 c6 ab aa  |Z...........$...|
00000010  66 69 72 6d 77 61 72 65  00 00 00 00 00 00 00 00  |firmware........|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000120  5a a5 d0 cf 00 00 00 00  00 00 00 00 00 00 00 00  |Z...............|
00000130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000

/tmp/k210.3_backup

This is backup boot configuration, 4096 bytes starting at 0x5000 in flash

It is identical to the main boot configuration above. Both are ./firmware/Kboot/build/config.bin, also contained in the kboot.kfpkg archive:

~/krux$ sha256sum /tmp/k210.[23]* ./firmware/Kboot/build/config.bin
c9b9c4adcabe9c353a907f44c101c3b29756b30762e4b282d87d2a92400e2198  /tmp/k210.2_main
c9b9c4adcabe9c353a907f44c101c3b29756b30762e4b282d87d2a92400e2198  /tmp/k210.3_backup
c9b9c4adcabe9c353a907f44c101c3b29756b30762e4b282d87d2a92400e2198  ./firmware/Kboot/build/config.bin

~/krux$ unzip -p ./build/kboot.kfpkg config.bin | sha256sum
c9b9c4adcabe9c353a907f44c101c3b29756b30762e4b282d87d2a92400e2198  -

/tmp/k210.4_reserved

This is a reserved / user data sector???

This is unchanged since flash was erased above.

~/krux$ hd /tmp/k210.4_reserved
00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
0000a000

/tmp/k210.5_app_user

This is default app and user data, 16711680 bytes starting at 0x10000 in flash

Logs seen while flashing krux firmware indicate that this really starts at 0x80000 so we'll expect an offset of 0x70000 in the dumped file.

~/krux$ hd /tmp/k210.5_app_user | head -5
00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00070000  00 00 2f 1d 00 21 a8 ef  be ad de 01 00 01 00 00  |../..!..........|
00070010  00 00 00 00 00 00 00 00  00 00 00 00 00 73 50 30  |.............sP0|
00070020  30 73 50 20 30 73 50 40  30 73 50 40 34 97 02 00  |0sP 0sP@0sP@4...|

The first 0x70000 bytes are indeed unchanged from when flash was originally erased.

~/krux$ hd ./build/firmware.bin | head -3
00000000  21 a8 ef be ad de 01 00  01 00 00 00 00 00 00 00  |!...............|
00000010  00 00 00 00 00 00 00 00  73 50 30 30 73 50 20 30  |........sP00sP 0|
00000020  73 50 40 30 73 50 40 34  97 02 00 00 93 82 82 0f  |sP@0sP@4........|

So it looks like our dump has a 5 byte header, but then we see what looks like firmware.bin

See https://github.com/loboris/ktool/blob/0345aa90d9b3830641373fb4e3ce4edf45d0a46f/ktool.py#L1161 Format is header + content + footer where header=AES byte + 4byte little endian size of content, and footer is sha256(header+content). ie: 0x001d2f00=1912576

We also learned that the length of the firmware write was 1912613 bytes long, exactly 37 bytes larger than ./build/firmware.bin because of the 5 byte header and 32 byte hash footer.

Is this really the firmware that we expect?

Some math for the flash dump offset: known offset of 0x70000 + 5 = 458757 to be skipped, and for the length, because the firmware is 1912576 bytes:

~/krux$ tail -c +458758 /tmp/k210.5_app_user | head -c 1912576 | sha256sum
f234f6b1b517af8b988980c395f482cf535d07c67ef978ca3cc175bd07aa54e8  -

~/krux$ sha256sum ./build/firmware.bin
f234f6b1b517af8b988980c395f482cf535d07c67ef978ca3cc175bd07aa54e8  ./build/firmware.bin

Yes, it is indeed ./build/firmware.bin

Immediately following the firmware is the 32 byte sha256 hash of all 1912581 bytes of firmware -- including prefixed 5 byte header, then 0x00 bytes to fill the block.

TODO explain the spurious bytes other than the expected 0xff values from the flash erase. I have a theory, that this is "filesystem" space. I now believe that I had an sdcard inserted when I originally did this analysis (but I could be mistaken). Since then, I've found settings.json in this space when re-running the same analysis, but I have yet to find any "secrets" or even unexplained bytes, which was the primary goal of this excercise... I'll certainly keep watching for that.

~/krux$ tail -c +458753 /tmp/k210.5_app_user | head -c 1912581 | sha256sum
9cd40f850a46b4537b4c49a847fa03d2fbf3fd03741ebe73dfe5093fb4463990  -

~/krux$ tail -c +2371334 /tmp/k210.5_app_user | hd
00000000  9c d4 0f 85 0a 46 b4 53  7b 4c 49 a8 47 fa 03 d2  |.....F.S{LI.G...|
00000010  fb f3 fd 03 74 1e be 73  df e5 09 3f b4 46 39 90  |....t..s...?.F9.|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
0000d0f0  00 00 00 00 00 00 00 00  00 00 00 ff ff ff ff ff  |................|
0000d100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00aae0f0  ff ff ff ff ff ff ff 31  15 00 00 ff ff ff ff ff  |.......1........|
00aae100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00ace0f0  ff ff ff ff ff ff ff 3e  15 00 00 ff ff ff ff ff  |.......>........|
00ace100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00aee0f0  ff ff ff ff ff ff ff 3f  15 00 00 ff ff ff ff ff  |.......?........|
00aee100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00b0e0f0  ff ff ff ff ff ff ff 3c  15 00 00 ff ff ff ff ff  |.......<........|
00b0e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00b2e0f0  ff ff ff ff ff ff ff 3d  15 00 00 ff ff ff ff ff  |.......=........|
00b2e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00b4e0f0  ff ff ff ff ff ff ff 3a  15 00 00 ff ff ff ff ff  |.......:........|
00b4e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00b6e0f0  ff ff ff ff ff ff ff 3b  15 00 00 ff ff ff ff ff  |.......;........|
00b6e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00b8e0f0  ff ff ff ff ff ff ff 38  15 00 00 ff ff ff ff ff  |.......8........|
00b8e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00bae0f0  ff ff ff ff ff ff ff 39  15 00 00 ff ff ff ff ff  |.......9........|
00bae100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00bce0f0  ff ff ff ff ff ff ff 26  15 00 00 ff ff ff ff ff  |.......&........|
00bce100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00bee0f0  ff ff ff ff ff ff ff 27  15 00 00 ff ff ff ff ff  |.......'........|
00bee100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00c0e0f0  ff ff ff ff ff ff ff 24  15 00 00 ff ff ff ff ff  |.......$........|
00c0e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00c2e0f0  ff ff ff ff ff ff ff 25  15 00 00 ff ff ff ff ff  |.......%........|
00c2e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00c4e0f0  ff ff ff ff ff ff ff 22  15 00 00 ff ff ff ff ff  |......."........|
00c4e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00c6e0f0  ff ff ff ff ff ff ff 23  15 00 00 ff ff ff ff ff  |.......#........|
00c6e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00c8e0f0  ff ff ff ff ff ff ff 20  15 00 00 ff ff ff ff ff  |....... ........|
00c8e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00cae0f0  ff ff ff ff ff ff ff 21  15 00 00 ff ff ff ff ff  |.......!........|
00cae100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00cce0f0  ff ff ff ff ff ff ff 2e  15 00 00 ff ff ff ff ff  |................|
00cce100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00cee0f0  ff ff ff ff ff ff ff 2f  15 00 00 ff ff ff ff ff  |......./........|
00cee100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00d0e0f0  ff ff ff ff ff ff ff 2c  15 00 00 ff ff ff ff ff  |.......,........|
00d0e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00d2e0f0  ff ff ff ff ff ff ff 2d  15 00 00 ff ff ff ff ff  |.......-........|
00d2e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00d4e0f0  ff ff ff ff ff ff ff 2a  15 00 00 ff ff ff ff ff  |.......*........|
00d4e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00d6e0f0  ff ff ff ff ff ff ff 2b  15 00 00 ff ff ff ff ff  |.......+........|
00d6e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00d8e0f0  ff ff ff ff ff ff ff 28  15 00 00 ff ff ff ff ff  |.......(........|
00d8e100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00dad0f0  ff ff ff ff ff ff ff ff  ff ff ff                 |...........|
00dad0fb

But have we truly been reading ALL of the flash?

And what exactly does Sipeed mean by "16MB Flash"?

Is it really 2**24 == 16777216 like has been assumed above?

If this truly were the physical limit, perhaps accessing any addresses equal to or greater than 0x1000000 would not have access to those higher address bits. If so we'd see errors, or we'd be wrapping back to the flash addresses starting at 0x0. Can we prove this by reading from any addresses that are a multiple of 16MB and find that we're really just getting copies of flash that we've already seen below 16MB?

~/krux$ python3 ./firmware/Kboot/build/ktool.py -B goE -R -a 0x4000000 -L 16777216 /tmp/k210.flash_dump
...
[INFO] Read 16777216 Bytes from Flash address 0x4000000 
Read Flash: |=============================================| 100.0% 11kiB/s
[INFO] Read 16777216 Bytes in 1470.181s 

~/krux$ sha256sum /tmp/k210.flash_dump
d5927418971e813890a50c0ac8492ca9a796a80ee2d47ad441eac50846cdcb85  /tmp/k210.flash_dump

~/krux$ cat /tmp/k210.[0-5]* | sha256sum
d5927418971e813890a50c0ac8492ca9a796a80ee2d47ad441eac50846cdcb85  -

It appears that any flash addresses with high-order bits beyond the 24th bit are indeed ignored. Accessing 16MB of flash as 2**24 does appear to be a physical upper limit of flash storage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment