@@ -63,7 +63,29 @@ struct TextFile {
6363//
6464//--------------------------------------------------------------------+
6565
66- #define NUM_FAT_BLOCKS CFG_UF2_NUM_BLOCKS
66+ #define BPB_SECTOR_SIZE ( 512)
67+ #define BPB_SECTORS_PER_CLUSTER ( 1)
68+ #define BPB_RESERVED_SECTORS ( 1)
69+ #define BPB_NUMBER_OF_FATS ( 2)
70+ #define BPB_ROOT_DIR_ENTRIES ( 64)
71+ #define BPB_TOTAL_SECTORS CFG_UF2_NUM_BLOCKS
72+ #define BPB_MEDIA_DESCRIPTOR_BYTE (0xF8)
73+ #define FAT_ENTRY_SIZE (2)
74+ #define FAT_ENTRIES_PER_SECTOR (BPB_SECTOR_SIZE / FAT_ENTRY_SIZE)
75+ // NOTE: MS specification explicitly allows FAT to be larger than necessary
76+ #define BPB_SECTORS_PER_FAT ( (BPB_TOTAL_SECTORS / FAT_ENTRIES_PER_SECTOR) + \
77+ ((BPB_TOTAL_SECTORS % FAT_ENTRIES_PER_SECTOR) ? 1 : 0))
78+ #define DIRENTRIES_PER_SECTOR (BPB_SECTOR_SIZE/sizeof(DirEntry))
79+ #define ROOT_DIR_SECTOR_COUNT (BPB_ROOT_DIR_ENTRIES/DIRENTRIES_PER_SECTOR)
80+
81+ STATIC_ASSERT (BPB_SECTOR_SIZE == 512 ); // GhostFAT does not support other sector sizes (currently)
82+ STATIC_ASSERT (BPB_SECTORS_PER_CLUSTER == 1 ); // GhostFAT presumes one sector == one cluster (for simplicity)
83+ STATIC_ASSERT (BPB_NUMBER_OF_FATS == 2 ); // FAT highest compatibility
84+ STATIC_ASSERT (sizeof (DirEntry ) == 32 ); // FAT requirement
85+ STATIC_ASSERT (BPB_SECTOR_SIZE % sizeof (DirEntry ) == 0 ); // FAT requirement
86+ STATIC_ASSERT (BPB_ROOT_DIR_ENTRIES % DIRENTRIES_PER_SECTOR == 0 ); // FAT requirement
87+ STATIC_ASSERT (BPB_SECTOR_SIZE * BPB_SECTORS_PER_CLUSTER <= (32 * 1024 )); // FAT requirement (64k+ has known compatibility problems)
88+ STATIC_ASSERT (FAT_ENTRIES_PER_SECTOR == 256 ); // FAT requirement
6789
6890#define STR0 (x ) #x
6991#define STR (x ) STR0(x)
@@ -90,50 +112,62 @@ static struct TextFile const info[] = {
90112 // current.uf2 must be the last element and its content must be NULL
91113 {.name = "CURRENT UF2" , .content = NULL },
92114};
93-
94- // code presumes each non-UF2 file content fits in single sector
95- // Cannot programmatically statically assert .content length
96- // for each element above.
97- STATIC_ASSERT (ARRAY_SIZE (indexFile ) < 512 );
98-
115+ STATIC_ASSERT (ARRAY_SIZE (infoUf2File ) < BPB_SECTOR_SIZE ); // GhostFAT requires files to fit in one sector
116+ STATIC_ASSERT (ARRAY_SIZE (indexFile ) < BPB_SECTOR_SIZE ); // GhostFAT requires files to fit in one sector
99117
100118#define NUM_FILES (ARRAY_SIZE(info))
101119#define NUM_DIRENTRIES (NUM_FILES + 1) // Code adds volume label as first root directory entry
120+ #define REQUIRED_ROOT_DIRECTORY_SECTORS ( ((NUM_DIRENTRIES+1) / DIRENTRIES_PER_SECTOR) + \
121+ (((NUM_DIRENTRIES+1) % DIRENTRIES_PER_SECTOR) ? 1 : 0))
122+ STATIC_ASSERT (ROOT_DIR_SECTOR_COUNT >= REQUIRED_ROOT_DIRECTORY_SECTORS ); // FAT requirement -- Ensures BPB reserves sufficient entries for all files
123+ STATIC_ASSERT (NUM_DIRENTRIES < (DIRENTRIES_PER_SECTOR * ROOT_DIR_SECTOR_COUNT )); // FAT requirement -- end directory with unused entry
124+ STATIC_ASSERT (NUM_DIRENTRIES < BPB_ROOT_DIR_ENTRIES ); // FAT requirement -- Ensures BPB reserves sufficient entries for all files
125+ STATIC_ASSERT (NUM_DIRENTRIES < DIRENTRIES_PER_SECTOR ); // GhostFAT bug workaround -- else, code overflows buffer
126+
127+ #define NUM_SECTORS_IN_DATA_REGION (BPB_TOTAL_SECTORS - BPB_RESERVED_SECTORS - (BPB_NUMBER_OF_FATS * BPB_SECTORS_PER_FAT) - ROOT_DIR_SECTOR_COUNT)
128+ #define CLUSTER_COUNT (NUM_SECTORS_IN_DATA_REGION / BPB_SECTORS_PER_CLUSTER)
129+
130+ // Ensure cluster count results in a valid FAT16 volume!
131+ STATIC_ASSERT ( CLUSTER_COUNT >= 0x0FF5 && CLUSTER_COUNT < 0xFFF5 );
132+
133+ // Many existing FAT implementations have small (1-16) off-by-one style errors
134+ // So, avoid being within 32 of those limits for even greater compatibility.
135+ STATIC_ASSERT ( CLUSTER_COUNT >= 0x1015 && CLUSTER_COUNT < 0xFFD5 );
136+
102137
103- #define UF2_SIZE ((USER_FLASH_END-USER_FLASH_START) * 2)
104- #define UF2_SECTORS (UF2_SIZE / 512)
105- #define UF2_FIRST_SECTOR (NUM_FILES + 1) // WARNING -- code presumes each non-UF2 file content fits in single sector
106- #define UF2_LAST_SECTOR (UF2_FIRST_SECTOR + UF2_SECTORS - 1)
138+ #define UF2_FIRMWARE_BYTES_PER_SECTOR 256
139+ #define TRUE_USER_FLASH_SIZE (USER_FLASH_END-USER_FLASH_START)
140+ STATIC_ASSERT (TRUE_USER_FLASH_SIZE % UF2_FIRMWARE_BYTES_PER_SECTOR == 0 ); // UF2 requirement -- overall size must be integral multiple of per-sector payload?
107141
108- #define RESERVED_SECTORS 1
109- #define ROOT_DIR_SECTORS 4
110- #define SECTORS_PER_FAT ((NUM_FAT_BLOCKS * 2 + 511) / 512 )
142+ #define UF2_SECTORS ( (TRUE_USER_FLASH_SIZE / UF2_FIRMWARE_BYTES_PER_SECTOR) + \
143+ ((TRUE_USER_FLASH_SIZE % UF2_FIRMWARE_BYTES_PER_SECTOR) ? 1 : 0))
144+ #define UF2_SIZE (UF2_SECTORS * BPB_SECTOR_SIZE )
111145
112- #define START_FAT0 RESERVED_SECTORS
113- #define START_FAT1 (START_FAT0 + SECTORS_PER_FAT)
114- #define START_ROOTDIR (START_FAT1 + SECTORS_PER_FAT)
115- #define START_CLUSTERS (START_ROOTDIR + ROOT_DIR_SECTORS)
146+ STATIC_ASSERT (UF2_SECTORS == ((UF2_SIZE /2 ) / 256 )); // Not a requirement ... ensuring replacement of literal value is not a change
116147
117- // all directory entries must fit in a single sector
118- // because otherwise current code overflows buffer
119- #define DIRENTRIES_PER_SECTOR (512/sizeof(DirEntry))
148+ #define UF2_FIRST_SECTOR ((NUM_FILES + 1) * BPB_SECTORS_PER_CLUSTER) // WARNING -- code presumes each non-UF2 file content fits in single sector
149+ #define UF2_LAST_SECTOR ((UF2_FIRST_SECTOR + UF2_SECTORS - 1) * BPB_SECTORS_PER_CLUSTER)
120150
121- STATIC_ASSERT (NUM_DIRENTRIES < DIRENTRIES_PER_SECTOR * ROOT_DIR_SECTORS );
151+ #define FS_START_FAT0_SECTOR BPB_RESERVED_SECTORS
152+ #define FS_START_FAT1_SECTOR (FS_START_FAT0_SECTOR + BPB_SECTORS_PER_FAT)
153+ #define FS_START_ROOTDIR_SECTOR (FS_START_FAT1_SECTOR + BPB_SECTORS_PER_FAT)
154+ #define FS_START_CLUSTERS_SECTOR (FS_START_ROOTDIR_SECTOR + ROOT_DIR_SECTOR_COUNT)
122155
123156
124157static FAT_BootBlock const BootBlock = {
125158 .JumpInstruction = {0xeb , 0x3c , 0x90 },
126159 .OEMInfo = "UF2 UF2 " ,
127- .SectorSize = 512 ,
128- .SectorsPerCluster = 1 ,
129- .ReservedSectors = RESERVED_SECTORS ,
130- .FATCopies = 2 ,
131- .RootDirectoryEntries = ( ROOT_DIR_SECTORS * DIRENTRIES_PER_SECTOR ) ,
132- .TotalSectors16 = NUM_FAT_BLOCKS - 2 ,
133- .MediaDescriptor = 0xF8 ,
134- .SectorsPerFAT = SECTORS_PER_FAT ,
160+ .SectorSize = BPB_SECTOR_SIZE ,
161+ .SectorsPerCluster = BPB_SECTORS_PER_CLUSTER ,
162+ .ReservedSectors = BPB_RESERVED_SECTORS ,
163+ .FATCopies = BPB_NUMBER_OF_FATS ,
164+ .RootDirectoryEntries = BPB_ROOT_DIR_ENTRIES ,
165+ .TotalSectors16 = ( BPB_TOTAL_SECTORS > 0xFFFF ) ? 0 : BPB_TOTAL_SECTORS ,
166+ .MediaDescriptor = BPB_MEDIA_DESCRIPTOR_BYTE ,
167+ .SectorsPerFAT = BPB_SECTORS_PER_FAT ,
135168 .SectorsPerTrack = 1 ,
136169 .Heads = 1 ,
170+ .TotalSectors32 = (BPB_TOTAL_SECTORS > 0xFFFF ) ? BPB_TOTAL_SECTORS : 0 ,
137171 .PhysicalDriveNum = 0x80 , // to match MediaDescriptor of 0xF8
138172 .ExtendedBootSig = 0x29 ,
139173 .VolumeSerialNumber = 0x00420042 ,
@@ -150,10 +184,13 @@ extern const uint32_t bootloaderConfig[];
150184//--------------------------------------------------------------------+
151185static inline bool is_uf2_block (UF2_Block const * bl )
152186{
153- return (bl -> magicStart0 == UF2_MAGIC_START0 ) && (bl -> magicStart1 == UF2_MAGIC_START1 ) &&
187+ return (bl -> magicStart0 == UF2_MAGIC_START0 ) &&
188+ (bl -> magicStart1 == UF2_MAGIC_START1 ) &&
154189 (bl -> magicEnd == UF2_MAGIC_END ) &&
155- (bl -> flags & UF2_FLAG_FAMILYID ) && !(bl -> flags & UF2_FLAG_NOFLASH ) &&
156- (bl -> payloadSize == 256 ) && !(bl -> targetAddr & 0xff );
190+ (bl -> flags & UF2_FLAG_FAMILYID ) &&
191+ !(bl -> flags & UF2_FLAG_NOFLASH ) &&
192+ (bl -> payloadSize == UF2_FIRMWARE_BYTES_PER_SECTOR ) &&
193+ !(bl -> targetAddr & 0xff );
157194}
158195
159196// used when upgrading application
@@ -207,45 +244,49 @@ void padded_memcpy (char *dst, char const *src, int len)
207244{
208245 for ( int i = 0 ; i < len ; ++ i )
209246 {
210- if ( * src )
247+ if ( * src ) {
211248 * dst = * src ++ ;
212- else
249+ } else {
213250 * dst = ' ' ;
251+ }
214252 dst ++ ;
215253 }
216254}
217255
218256void read_block (uint32_t block_no , uint8_t * data ) {
219- memset (data , 0 , 512 );
257+ memset (data , 0 , BPB_SECTOR_SIZE );
220258 uint32_t sectionIdx = block_no ;
221259
222260 if (block_no == 0 ) { // Requested boot block
223261 memcpy (data , & BootBlock , sizeof (BootBlock ));
224- data [510 ] = 0x55 ;
225- data [511 ] = 0xaa ;
262+ data [510 ] = 0x55 ; // Always at offsets 510/511, even when BPB_SECTOR_SIZE is larger
263+ data [511 ] = 0xaa ; // Always at offsets 510/511, even when BPB_SECTOR_SIZE is larger
226264 // logval("data[0]", data[0]);
227- } else if (block_no < START_ROOTDIR ) { // Requested FAT table sector
228- sectionIdx -= START_FAT0 ;
265+ } else if (block_no < FS_START_ROOTDIR_SECTOR ) { // Requested FAT table sector
266+ sectionIdx -= FS_START_FAT0_SECTOR ;
229267 // logval("sidx", sectionIdx);
230- if (sectionIdx >= SECTORS_PER_FAT )
231- sectionIdx -= SECTORS_PER_FAT ; // second FAT is same as the first...
268+ if (sectionIdx >= BPB_SECTORS_PER_FAT ) {
269+ sectionIdx -= BPB_SECTORS_PER_FAT ; // second FAT is same as the first...
270+ }
232271 if (sectionIdx == 0 ) {
233- data [0 ] = 0xf8 ; // first FAT entry must match BPB MediaDescriptor
272+ // first FAT entry must match BPB MediaDescriptor
273+ data [0 ] = BPB_MEDIA_DESCRIPTOR_BYTE ;
234274 // WARNING -- code presumes only one NULL .content for .UF2 file
235275 // and all non-NULL .content fit in one sector
236276 // and requires it be the last element of the array
237- for (uint32_t i = 1 ; i < NUM_FILES * 2 + 4 ; ++ i ) {
277+ uint32_t const end = (NUM_FILES * FAT_ENTRY_SIZE ) + (2 * FAT_ENTRY_SIZE );
278+ for (uint32_t i = 1 ; i < end ; ++ i ) {
238279 data [i ] = 0xff ;
239280 }
240281 }
241- for (uint32_t i = 0 ; i < 256 ; ++ i ) { // Generate the FAT chain for the firmware "file"
242- uint32_t v = sectionIdx * 256 + i ;
282+ for (uint32_t i = 0 ; i < FAT_ENTRIES_PER_SECTOR ; ++ i ) { // Generate the FAT chain for the firmware "file"
283+ uint32_t v = ( sectionIdx * FAT_ENTRIES_PER_SECTOR ) + i ;
243284 if (UF2_FIRST_SECTOR <= v && v <= UF2_LAST_SECTOR )
244285 ((uint16_t * )(void * )data )[i ] = v == UF2_LAST_SECTOR ? 0xffff : v + 1 ;
245286 }
246- } else if (block_no < START_CLUSTERS ) { // Requested root directory sector
287+ } else if (block_no < FS_START_CLUSTERS_SECTOR ) { // Requested root directory sector
247288
248- sectionIdx -= START_ROOTDIR ;
289+ sectionIdx -= FS_START_ROOTDIR_SECTOR ;
249290
250291 DirEntry * d = (void * )data ;
251292 int remainingEntries = DIRENTRIES_PER_SECTOR ;
@@ -270,35 +311,37 @@ void read_block(uint32_t block_no, uint8_t *data) {
270311 d -> createTime = __DOSTIME__ ;
271312 d -> createDate = __DOSDATE__ ;
272313 d -> lastAccessDate = __DOSDATE__ ;
273- d -> highStartCluster = startCluster >> 8 ;
314+ d -> highStartCluster = startCluster >> 16 ;
274315 // DIR_WrtTime and DIR_WrtDate must be supported
275316 d -> updateTime = __DOSTIME__ ;
276317 d -> updateDate = __DOSDATE__ ;
277- d -> startCluster = startCluster & 0xFF ;
318+ d -> startCluster = startCluster & 0xFFFF ;
278319 d -> size = (inf -> content ? strlen (inf -> content ) : UF2_SIZE );
279320 }
280321
281- } else {
282- sectionIdx -= START_CLUSTERS ;
322+ } else if (block_no < BPB_TOTAL_SECTORS ) {
323+
324+ sectionIdx -= FS_START_CLUSTERS_SECTOR ;
283325 if (sectionIdx < NUM_FILES - 1 ) {
284326 memcpy (data , info [sectionIdx ].content , strlen (info [sectionIdx ].content ));
285327 } else { // generate the UF2 file data on-the-fly
286328 sectionIdx -= NUM_FILES - 1 ;
287- uint32_t addr = USER_FLASH_START + sectionIdx * 256 ;
329+ uint32_t addr = USER_FLASH_START + ( sectionIdx * UF2_FIRMWARE_BYTES_PER_SECTOR ) ;
288330 if (addr < CFG_UF2_FLASH_SIZE ) {
289331 UF2_Block * bl = (void * )data ;
290332 bl -> magicStart0 = UF2_MAGIC_START0 ;
291333 bl -> magicStart1 = UF2_MAGIC_START1 ;
292334 bl -> magicEnd = UF2_MAGIC_END ;
293335 bl -> blockNo = sectionIdx ;
294- bl -> numBlocks = ( UF2_SIZE / 2 ) / 256 ;
336+ bl -> numBlocks = UF2_SECTORS ;
295337 bl -> targetAddr = addr ;
296- bl -> payloadSize = 256 ;
338+ bl -> payloadSize = UF2_FIRMWARE_BYTES_PER_SECTOR ;
297339 bl -> flags = UF2_FLAG_FAMILYID ;
298340 bl -> familyID = CFG_UF2_FAMILY_APP_ID ;
299341 memcpy (bl -> data , (void * )addr , bl -> payloadSize );
300342 }
301343 }
344+
302345 }
303346}
304347
@@ -310,7 +353,7 @@ void read_block(uint32_t block_no, uint8_t *data) {
310353 * Write an uf2 block wrapped by 512 sector.
311354 * @return number of bytes processed, only 3 following values
312355 * -1 : if not an uf2 block
313- * 512 : write is successful
356+ * 512 : write is successful (BPB_SECTOR_SIZE == 512)
314357 * 0 : is busy with flashing, tinyusb stack will call write_block again with the same parameters later on
315358 */
316359int write_block (uint32_t block_no , uint8_t * data , WriteState * state )
@@ -518,5 +561,6 @@ int write_block (uint32_t block_no, uint8_t *data, WriteState *state)
518561 }
519562 }
520563
521- return 512 ;
564+ STATIC_ASSERT (BPB_SECTOR_SIZE == 512 ); // if sector size changes, may need to re-validate this code
565+ return BPB_SECTOR_SIZE ;
522566}
0 commit comments