PicoMite 6.03.00 betas


Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 11472
Posted: 05:37pm 22 Apr 2026      

For javavi

ME:
  Quote  A user is using the HDMIUSB variant of the firmware. He has connected a second Pico in USB boot mode where it appears as a C: drive. He can copy uf2 files to the second Pico successfully as long as they are less than 1MB (1024x1024) and the second pico then reboots and appears as a cdc device as expected. Above 1MB the uf2 files do not copy successfully. Can you identify any reason for this.

CLAUDE:
  Quote  The root cause is in the interaction between FatFS's FAT window mechanism and the RP2040 bootrom's virtual FAT disk.

Root cause: FatFS FAT-window vs. RP2040 bootrom's non-persistent FAT
How the RP2040 bootrom handles writes
The RP2040 in BOOTSEL mode presents a virtual FAT16 disk. When it receives USB MSC sector writes:

If the sector contains a valid UF2 magic (0x0A324655 / 0x9E5D5157): the payload is extracted and programmed to flash.
Any other sector write (including FAT, directory, BPB): silently discarded — the bootrom has no backing store for them.
The bootrom always returns the original, read-only FAT when those sectors are read back.

FatFS uses a single-sector FAT window
In ff.c, move_window() keeps exactly one 512-byte sector of the FAT in fs->win[]:


static FRESULT move_window(FATFS* fs, LBA_t sect)
{
   if (sect != fs->winsect) {
       res = sync_window(fs);          // flush current window → WRITTEN TO DISK
       disk_read(fs->pdrv, fs->win, sect, 1);  // load new sector ← READ FROM DISK
       fs->winsect = sect;
   }
}
Each FAT sector holds 256 FAT16 entries (512 bytes ÷ 2 bytes/entry).

The 1 MB boundary
The RP2040 bootrom's 128 MB virtual disk uses 4 KB clusters (8 sectors/cluster — the only cluster size that fits ≤65524 clusters into a 128 MB FAT16 volume). One FAT sector therefore covers:


256 clusters × 4096 bytes/cluster = 1,048,576 bytes = 1 MB exactly
For files ≤ 1 MB (≤ 256 clusters): all cluster-chain entries live in FAT sector 1. FatFS keeps it in win[] throughout the write and never needs to swap the window. The FAT flush happens only at f_close(), by which time every UF2 block has already been written to the data area and processed by the bootrom. Result: success.

For files > 1 MB (> 256 clusters): FatFS needs FAT sector 2 for clusters 256+. move_window() flushes FAT sector 1 to disk (bootrom discards it) and reads FAT sector 2. Later, when it needs to write the cluster 255→256 chain link (which straddles the sector boundary), it swaps back to FAT sector 1 — and reads the original all-free data. FatFS now believes clusters 2-255 are free and tries to re-allocate them, overwriting data sectors or corrupting the chain. Result: failure.

Secondary confirmation
You can verify the cluster size yourself. Sector 0 of the C: drive is the BPB. Byte offset 13 (0x0D) is BPB_SecPerClus. If it reads 8, cluster size = 4096 bytes and the analysis above is exact.

Fix
The FatFS layer is the wrong tool for writing to an RP2040 boot drive: the bootrom ignores FAT structure entirely. The correct approach is to bypass FatFS for this transfer:

Read the UF2 source file from SD card normally with f_read.
Write each 512-byte UF2 block directly to the USB drive using disk_write(pdrv=1, buff, start_lba + block_idx, 1), starting at an LBA inside the data area (well past the FAT sectors — any LBA ≥ ~300 is safe for the 128 MB layout).
The bootrom processes each write as it arrives, regardless of which LBA it lands on.
This completely sidesteps the FAT chain management and would remove the 1 MB limit. It would need a dedicated MMBasic command (e.g., FLASH PICO C: B:/firmware.uf2) rather than reusing COPY, since COPY always goes through FatFS on the destination.

ME:
  Quote  This is interesting, how does W11 and Linux deal with this?

CLAUDE:
  Quote  Both Windows 11 and Linux are immune to this problem, for the same fundamental reason: they never read FAT data back from disk once they've written it.

The page cache difference
When a PC OS writes a file to any FAT volume, it maintains the entire FAT in its page cache (Windows calls this the system cache / FastFAT cache; Linux uses the kernel buffer cache). The flow is:

OS reads the BPB, FAT sectors, and root directory — all go into the page cache.
OS allocates clusters — updates are made in the page-cached copy of the FAT, never flushed to disk yet.
OS writes data sectors to disk — the bootrom receives each sector, sees UF2 magic, programs flash.
OS marks the FAT pages dirty — eventually flushed to disk, but that's just metadata cleanup.
By the time the FAT is flushed (if at all), all UF2 blocks have already been received and programmed.
When the OS needs to find the next free cluster for step 2, it scans its in-memory page-cached FAT, which correctly reflects every allocation it just made. It never does a disk round-trip to ask "is cluster 257 free?" — it already knows, from its own cache.

FatFS has no page cache. Its win[] buffer is exactly 512 bytes. The instant it needs to switch from FAT sector 1 to FAT sector 2, it must disk_read() sector 1 when it comes back — and the bootrom hands back the original all-free data.

Write ordering also helps
Windows and Linux typically write data sectors first, then update the FAT and directory entry afterwards (or asynchronously). This means the bootrom has received and programmed every UF2 block before the FAT is even touched. The FAT flush is essentially a no-op as far as the bootrom is concerned — it's just tidying up a virtual disk that's about to disappear anyway (the Pico reboots and unmounts).

Why the RP2040 bootrom was designed this way
Raspberry Pi designed the bootrom specifically assuming a host with a page cache. The design relies on two properties:

UF2 blocks carry their own address (target flash address is in the block itself), so they can arrive at any LBA in any order — no FAT traversal is needed by the bootrom.
The virtual FAT is read-only from the bootrom's perspective — it exists only to make the device mountable and to let Windows/macOS/Linux discover free space and allocate sectors. The FAT write-back is always ignored.
This design works perfectly for all mainstream OS FAT drivers but breaks FatFS because FatFS is the only common FAT implementation with a single-sector window that forces disk round-trips on every window switch.

Summary table
Windows 11 / Linux FatFS (PicoMite)
FAT backing store Page cache (entire FAT in RAM) Single 512-byte win[]
Re-reads own FAT writes? Never — reads from cache Yes — disk_read() on window switch
Immune to bootrom's non-persistent FAT? Yes No, breaks at 256 clusters
Large UF2 files Always work Fail above 1 cluster-sector boundary
The fix for PicoMite is to bypass FatFS for the write direction on the boot drive, writing raw UF2 sectors directly via disk_write() so there's no FAT chain management at all.

ME:
  Quote  Implement a Flash Pico command. The C: isn't necessary as the flash drive will always be C:. The uf2 filename will be on the A: or B: drive and will need normal filename parsing and path determination

CLAUDE:
  Quote  Syntax:
FLASH PICO B:/firmware.uf2     ' explicit drive + path
FLASH PICO firmware             ' current drive, adds .uf2 automatically
FLASH PICO A:/stored.uf2        ' from internal flash filesystem

PicoMiteHDMIUSB.zip