px-fwlib 0.10.0
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers generated with Doxygen 1.9.2
px_uf2.c
1/* =============================================================================
2 ____ ___ ____ ___ _ _ ___ __ __ ___ __ __ TM
3 | _ \ |_ _| / ___| / _ \ | \ | | / _ \ | \/ | |_ _| \ \/ /
4 | |_) | | | | | | | | | | \| | | | | | | |\/| | | | \ /
5 | __/ | | | |___ | |_| | | |\ | | |_| | | | | | | | / \
6 |_| |___| \____| \___/ |_| \_| \___/ |_| |_| |___| /_/\_\
7
8 Copyright (c) 2019 Pieter Conradie <https://piconomix.com>
9
10 License: MIT
11 https://github.com/piconomix/px-fwlib/blob/master/LICENSE.md
12
13 Title: px_uf2.h : Microsoft UF2 bootloader over USB MSC (Mass Storage Class)
14 Author(s): Pieter Conradie
15 Creation Date: 2019-05-25
16
17============================================================================= */
18
19/* _____STANDARD INCLUDES____________________________________________________ */
20
21/* _____PROJECT INCLUDES_____________________________________________________ */
22#include "px_uf2.h"
23#include "px_uf2_defs.h"
24#include "px_log.h"
25
26/* _____LOCAL DEFINITIONS____________________________________________________ */
27PX_LOG_NAME("px_uf2");
28
29/// Content of "info_uf2.txt"
30static const char px_uf2_file_content_info[] =
31 "UF2 Bootloader " PX_UF2_CFG_INFO_VERSION "\r\n"
32 "Model: " PX_UF2_CFG_INFO_MODEL "\r\n"
33 "Board-ID: " PX_UF2_CFG_INFO_BOARD_ID "\r\n";
34
35// Content of "index.htm"
36const char px_uf2_file_content_index_htm[] =
37 "<!doctype html>\n"
38 "<html>"
39 "<body>"
40 "<script>\n"
41 "location.replace(\"" PX_UF2_CFG_INDEX_URL "\");\n"
42 "</script>"
43 "</body>"
44 "</html>\n";
45
46/// List of files in root directory
47static const px_uf2_file_t px_uf2_file[] =
48{
49 {.name = "INFO_UF2TXT", .content = px_uf2_file_content_info},
50 {.name = "INDEX HTM", .content = px_uf2_file_content_index_htm},
51 {.name = "CURRENT UF2", .content = NULL},
52};
53
54/// Number of media sectors (needs to be more than ~4200 to force FAT16)
55#define PX_UF2_MEDIA_SECTORS 16000
56/// Sector size is fixed at 512 bytes
57#define PX_UF2_SECTOR_SIZE 512
58/// Payload size is fixed at 256 bytes
59#define PX_UF2_PAYLOAD_SIZE 256
60/// Number of files in root directory
61#define PX_UF2_FILES PX_SIZEOF_ARRAY(px_uf2_file)
62/// Number of root directory entries (includes volume label as first entry)
63#define PX_UF2_DIR_ENTRIES (PX_UF2_FILES + 1)
64
65/// UF2 file size in bytes (256 bytes of FLASH per UF2 block / sector)
66#define PX_UF2_FILE_SIZE (PX_UF2_CFG_FLASH_SIZE * 2)
67/// UF2 file size in sectors
68#define PX_UF2_FILE_SIZE_SECTORS (PX_UF2_FILE_SIZE / PX_UF2_SECTOR_SIZE)
69/// First sector of "current.uf2" file content; previous text file contents must fit in one sector each
70#define PX_UF2_FIRST_SECTOR (PX_UF2_FILES + 1)
71/// Last sector of "current.uf2" file content
72#define PX_UF2_LAST_SECTOR (PX_UF2_FIRST_SECTOR + PX_UF2_FILE_SIZE_SECTORS - 1)
73
74/// Number of FAT reserved sectors (contains boot block)
75#define PX_UF2_RESERVED_SECTORS 1
76/// Number of sectors per FAT
77#define PX_UF2_SECTORS_PER_FAT PX_UDIV_ROUNDUP(PX_UF2_MEDIA_SECTORS * 2, PX_UF2_SECTOR_SIZE)
78/// Number of root directory sectors
79#define PX_UF2_ROOT_DIR_SECTORS 4
80/// Number of directory entries per sector
81#define PX_UF2_DIR_ENTRIES_PER_SECTOR (PX_UF2_SECTOR_SIZE / sizeof(px_uf2_fat_dir_entry_t))
82
83/// FAT0 section starts after reserved sectors
84#define PX_UF2_START_FAT0 PX_UF2_RESERVED_SECTORS
85/// FAT1 section starts after FAT0
86#define PX_UF2_START_FAT1 (PX_UF2_START_FAT0 + PX_UF2_SECTORS_PER_FAT)
87/// Root directory region starts after FAT1
88#define PX_UF2_START_ROOT_DIR (PX_UF2_START_FAT1 + PX_UF2_SECTORS_PER_FAT)
89/// File & directory data region start after root directory
90#define PX_UF2_START_CLUSTERS (PX_UF2_START_ROOT_DIR + PX_UF2_ROOT_DIR_SECTORS)
91
92
93/// Content of FAT16 boot block in reserved section (first sector on media)
94static const px_uf2_fat_boot_block_t px_uf2_fat_boot_block =
95{
96 .jmp_boot = {0xeb, 0x3c, 0x90},
97 .oem_name = "UF2 UF2 ",
98 .bytes_per_sector = PX_UF2_SECTOR_SIZE,
99 .sectors_per_cluster = 1,
100 .res_sector_count = PX_UF2_RESERVED_SECTORS,
101 .num_fats = 2,
102 .root_dir_entry_count = PX_UF2_ROOT_DIR_SECTORS * PX_UF2_DIR_ENTRIES_PER_SECTOR,
103 .total_sectors_16 = PX_UF2_MEDIA_SECTORS - 2,
104 .media = 0xf0,
105 .fat_sectors_16 = PX_UF2_SECTORS_PER_FAT,
106 .sectors_per_track = 1,
107 .num_of_heads = 1,
108 .hidden_sectors = 0,
109 .total_sectors_32 = 0,
110 .drive_num = 0x80,
111 .reserved = 0,
112 .boot_sig = 0x29,
113 .volume_id = 0x00420042,
114 .volume_label = PX_UF2_CFG_VOLUME_LABEL,
115 .file_system_type = "FAT16 ",
116};
117
118#define UF2_MAX_BLOCKS (PX_UF2_CFG_FLASH_SIZE / 256 + 100)
119typedef struct
120{
121 uint32_t nr_of_blocks;
122 uint32_t num_written;
123 uint8_t written_mask[UF2_MAX_BLOCKS / 8 + 1];
125
126/* _____MACROS_______________________________________________________________ */
127
128/* _____GLOBAL VARIABLES_____________________________________________________ */
129
130/* _____LOCAL VARIABLES______________________________________________________ */
131/// Pointer to function to handle write to flash
132static px_uf2_on_wr_flash_block_t px_uf2_on_wr_flash_block;
133
134/// Pointer to function called when last block has been written
135static px_uf2_on_wr_flash_done_t px_uf2_on_wr_flash_done;
136
137/// Structure to track state of UF2 blocks written to FLASH
138static px_uf2_write_state_t px_uf2_write_state;
139
140/* _____LOCAL FUNCTION DECLARATIONS__________________________________________ */
141
142/* _____LOCAL FUNCTIONS______________________________________________________ */
143static void px_uf2_padded_memcpy(char * dst, const char * src, int len)
144{
145 for(int i = 0; i < len; i++)
146 {
147 if(*src != 0)
148 {
149 *dst = *src++;
150 }
151 else
152 {
153 *dst = ' ';
154 }
155 dst++;
156 }
157}
158
159/* _____GLOBAL FUNCTIONS_____________________________________________________ */
161 px_uf2_on_wr_flash_done_t on_wr_flash_done)
162{
163 px_uf2_on_wr_flash_block = on_wr_flash_block;
164 px_uf2_on_wr_flash_done = on_wr_flash_done;
165}
166
167void px_uf2_on_rd_sector(uint32_t sector_adr, uint8_t * buf)
168{
169 uint32_t section_index = sector_adr;
170
171 // Clear buffer
172 memset(buf, 0, PX_UF2_SECTOR_SIZE);
173
174 // Read of boot sector in reserved section?
175 if(sector_adr == 0)
176 {
177 // Return FAT16 boot sector info
178 memcpy(buf, &px_uf2_fat_boot_block, sizeof(px_uf2_fat_boot_block));
179 // Set signature word at the end of sector
180 buf[510] = 0x55;
181 buf[511] = 0xaa;
182 }
183
184 // Read of FAT table?
185 else if(sector_adr < PX_UF2_START_ROOT_DIR)
186 {
187 // Adjust index to start of FAT0
188 section_index -= PX_UF2_START_FAT0;
189 // Index in FAT1?
190 if(section_index >= PX_UF2_SECTORS_PER_FAT)
191 {
192 // FAT1 is the same as FAT0
193 section_index -= PX_UF2_SECTORS_PER_FAT;
194 }
195
196 // Read of first sector in FAT?
197 if(section_index == 0)
198 {
199 // FAT[0] contains the BPB_Media byte value in its low 8 bits, and all other bits are set to 1.
200 // FAT[1] is set by the format utility to the EOC value (0xffff)
201 // Mark 16-bit cluster entries of files as allocated and final (0xffff = end-of-file)
202 // Rest of clusters are free (0x0000)
203 buf[0] = px_uf2_fat_boot_block.media;
204 for(int i = 1; i < (PX_UF2_FILES * 2 + 4); i++)
205 {
206 buf[i] = 0xff;
207 }
208 }
209 // Calculate each cluster entry in this FAT sector
210 for(int i = 0; i < 256; i++)
211 {
212 uint32_t cluster = section_index * 256 + i;
213 // Is this cluster part of UF2 file?
214 if( (cluster >= PX_UF2_FIRST_SECTOR)
215 &&(cluster <= PX_UF2_LAST_SECTOR ) )
216 {
217 // Not last sector?
218 if(cluster != PX_UF2_LAST_SECTOR)
219 {
220 // Point cluster entry to next cluster (form singly linked list)
221 ((uint16_t *)buf)[i] = cluster + 1;
222 }
223 else
224 {
225 // Mark last cluster as allocated and final (end-of-file)
226 ((uint16_t *)buf)[i] = 0xffff;
227 }
228 }
229 }
230 }
231
232 // Read block from root directory?
233 else if(sector_adr < PX_UF2_START_CLUSTERS)
234 {
235 // Adjust index to start of root directory
236 section_index -= PX_UF2_START_ROOT_DIR;
237 // First sector in root directory?
238 if(section_index == 0)
239 {
241
242 // Set first directory entry to volume label
243 px_uf2_padded_memcpy(dir_entry->name, (char *)px_uf2_fat_boot_block.volume_label, 11);
244 dir_entry->attrs = PX_UF2_FAT_DIR_ATTR_VOLUME_ID | PX_UF2_FAT_DIR_ATTR_ARCHIVE;
245 // Create root directory entries from table
246 for(int i = 0; i < PX_UF2_FILES; i++)
247 {
248 const px_uf2_file_t * file = &px_uf2_file[i];
249 dir_entry++;
250
251 if(file->content != NULL)
252 {
253 dir_entry->size = strlen(file->content);
254 }
255 else
256 {
257 dir_entry->size = PX_UF2_FILE_SIZE;
258 }
259 dir_entry->start_cluster = i + 2;
260 px_uf2_padded_memcpy(dir_entry->name, file->name, 11);
261 dir_entry->create_date = 0x4d99;
262 dir_entry->update_date = 0x4d99;
263 }
264 }
265 }
266
267 // Read from file & directory data region
268 else
269 {
270 // Adjust index to start of region
271 section_index -= PX_UF2_START_CLUSTERS;
272 // Each file occupies exactly one sector
273 if(section_index < PX_UF2_FILES - 1)
274 {
275 // Return file content
276 memcpy(buf,
277 px_uf2_file[section_index].content,
278 strlen(px_uf2_file[section_index].content));
279 }
280 else
281 {
282 // Return UF2 file content
283 section_index -= PX_UF2_FILES - 1;
284 uint32_t addr = section_index * 256;
285 if (addr < PX_UF2_CFG_FLASH_SIZE)
286 {
287 // Fill UF2 fields with section of FLASH
288 px_uf2_block_t *bl = (void *)buf;
289
290 bl->magic_start0 = PX_UF2_MAGIC_START0;
291 bl->magic_start1 = PX_UF2_MAGIC_START1;
292 bl->flags = 0;
293 bl->target_addr = addr;
294 bl->payload_size = 256;
295 bl->block_nr = section_index;
296 bl->nr_of_blocks = PX_UF2_CFG_FLASH_SIZE / 256;
297 bl->flags |= PX_UF2_FLAG_FAMILYID_PRESENT;
298 bl->family_id = PX_UF2_CFG_FAMILY_ID;
299 memcpy(bl->data, (void *)addr, bl->payload_size);
300 bl->magic_end = PX_UF2_MAGIC_END;
301 }
302 }
303 }
304}
305
306void px_uf2_on_wr_sector(uint32_t sector_adr, const uint8_t * buf)
307{
308 px_uf2_block_t *bl = (void *)buf;
309
310 // Valid UF2 magic markers?
311 if( (bl->magic_start0 != PX_UF2_MAGIC_START0)
312 ||(bl->magic_start1 != PX_UF2_MAGIC_START1)
313 ||(bl->magic_end != PX_UF2_MAGIC_END ) )
314 {
315 // No. Ignore
316 return;
317 }
318
319 // Correct Family ID present?
320 if( ((bl->flags & PX_UF2_FLAG_FAMILYID_PRESENT) == 0)
321 ||(bl->family_id != PX_UF2_CFG_FAMILY_ID ) )
322 {
323 // No. Ignore
324 return;
325 }
326
327 // Valid UF2 flash address and payload size?
328 if( (bl->flags & PX_UF2_FLAG_NOFLASH )
329 ||(bl->payload_size != PX_UF2_PAYLOAD_SIZE )
330 ||(bl->target_addr & (PX_UF2_PAYLOAD_SIZE - 1) )
331 ||(bl->target_addr < PX_UF2_CFG_FLASH_START_ADR)
332 ||(bl->target_addr >= PX_UF2_CFG_FLASH_SIZE ) )
333 {
334 // this happens when we're trying to re-flash "current.uf2" file previously
335 // copied from a device; we still want to count these blocks to reset properly
336 PX_LOG_E("Invalid target address 0x%08X", bl->target_addr);
337 }
338 else
339 {
340 PX_LOG_D("Write 0x%08X %u bytes", bl->target_addr, bl->payload_size);
341 (*px_uf2_on_wr_flash_block)(bl->data, bl->target_addr, bl->payload_size);
342 }
343
344 if(bl->nr_of_blocks == 0)
345 {
346 return;
347 }
348
349 // Is this the first block?
350 if(px_uf2_write_state.nr_of_blocks != bl->nr_of_blocks)
351 {
352 if(bl->nr_of_blocks >= UF2_MAX_BLOCKS || px_uf2_write_state.nr_of_blocks)
353 {
354 // Reset number of blocks to max
355 px_uf2_write_state.nr_of_blocks = 0xffffffff;
356 }
357 else
358 {
359 // Save number of blocks to write
360 px_uf2_write_state.nr_of_blocks = bl->nr_of_blocks;
361 }
362 }
363
364 // Valid block?
365 if(bl->block_nr < UF2_MAX_BLOCKS)
366 {
367 uint32_t pos = bl->block_nr / 8;
368 uint8_t mask = 1 << (bl->block_nr % 8);
369
370 // Has block been written before?
371 if(!(px_uf2_write_state.written_mask[pos] & mask))
372 {
373 // Set bit to indicate that block has been written
374 px_uf2_write_state.written_mask[pos] |= mask;
375 // Increment count of blocks written
376 px_uf2_write_state.num_written++;
377 // Have all blocks been written?
378 if(px_uf2_write_state.num_written >= px_uf2_write_state.nr_of_blocks)
379 {
380 // Signal that las block has been written
381 PX_LOG_D("Write done");
382 (*px_uf2_on_wr_flash_done)();
383 }
384 }
385 }
386}
387
#define NULL
NULL pointer.
Definition: px_defs.h:49
#define PX_LOG_D(format,...)
Macro to display a formatted DEBUG message.
Definition: px_log.h:410
#define PX_LOG_NAME(name)
Macro to declare a log name string once for each file to reduce code size.
Definition: px_log.h:349
#define PX_LOG_E(format,...)
Macro to display a formatted ERROR message.
Definition: px_log.h:377
#define PX_UF2_CFG_INFO_VERSION
Info file version text.
void px_uf2_on_wr_sector(uint32_t sector_adr, const uint8_t *buf)
Function that must be called when a 512 byte sector is written over USB Mass Storage.
Definition: px_uf2.c:306
void px_uf2_on_rd_sector(uint32_t sector_adr, uint8_t *buf)
Function that must be called when a 512 byte sector is read over USB Mass Storage.
Definition: px_uf2.c:167
#define PX_UF2_CFG_INDEX_URL
index.htm file URL
void px_uf2_init(px_uf2_on_wr_flash_block_t on_wr_flash_block, px_uf2_on_wr_flash_done_t on_wr_flash_done)
Initialise UF2 module.
Definition: px_uf2.c:160
#define PX_UF2_CFG_INFO_BOARD_ID
Info file board ID text.
#define PX_UF2_CFG_FAMILY_ID
Family ID.
#define PX_UF2_CFG_INFO_MODEL
Info file model text.
#define PX_UF2_CFG_FLASH_START_ADR
Start address of flash to write to (reserved space for bootloader)
#define PX_UF2_CFG_VOLUME_LABEL
FAT16 volume label.
void(* px_uf2_on_wr_flash_done_t)(void)
Definition of a pointer to a function that will be called when last block has been written.
Definition: px_uf2.h:89
#define PX_UF2_CFG_FLASH_SIZE
Flash size.
void(* px_uf2_on_wr_flash_block_t)(const uint8_t *data, uint32_t adr, size_t nr_of_bytes)
Definition of a pointer to a function that will be called to write a block of data to flash.
Definition: px_uf2.h:81
Definition: px_uf2_defs.h:85