diff --git a/README.md b/README.md
index 5644f60..011d222 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ CherryUSB Device Stack has the following functions:
- Support Remote NDIS (RNDIS)
- Support WINUSB1.0、WINUSB2.0(with BOS)
- Support Vendor class
+- Support UF2
- Support multi device with the same USB IP
CherryUSB Device Stack resource usage (GCC 10.2 with -O2):
diff --git a/README_zh.md b/README_zh.md
index eb804f3..59e623e 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -67,6 +67,7 @@ CherryUSB Device 协议栈当前实现以下功能:
- 支持 Remote NDIS (RNDIS)
- 支持 WINUSB1.0、WINUSB2.0(带 BOS )
- 支持 Vendor 类 class
+- 支持 UF2
- 支持相同 USB IP 的多从机
CherryUSB Device 协议栈资源占用说明(GCC 10.2 with -O2):
diff --git a/demo/bootuf2/bootuf2.c b/demo/bootuf2/bootuf2.c
new file mode 100644
index 0000000..b992c4b
--- /dev/null
+++ b/demo/bootuf2/bootuf2.c
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2024, sakumisu
+ * Copyright (c) 2024, Egahp
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "bootuf2.h"
+#include "usbd_core.h"
+
+char file_INFO[] = {
+ "CherryUSB UF2 BOOT\r\n"
+ "Model: " CONFIG_PRODUCT "\r\n"
+ "Board-ID: " CONFIG_BOARD "\r\n"
+};
+
+const char file_IDEX[] = {
+ "\n"
+ ""
+ "
"
+ ""
+ ""
+ "\n"
+};
+
+const char file_JOIN[] = {
+ "\n"
+ ""
+ ""
+ ""
+ ""
+ "\n"
+};
+
+const char file_ID__[12] = BOOTUF2_FAMILYID_ARRAY;
+
+static struct bootuf2_FILE files[] = {
+ [0] = { .Name = file_ID__, .Content = NULL, .FileSize = 0 },
+ [1] = { .Name = "INFO_UF2TXT", .Content = file_INFO, .FileSize = sizeof(file_INFO) - 1 },
+ [2] = { .Name = "INDEX HTM", .Content = file_IDEX, .FileSize = sizeof(file_IDEX) - 1 },
+ [3] = { .Name = "JOIN HTM", .Content = file_JOIN, .FileSize = sizeof(file_JOIN) - 1 },
+};
+
+/*!< define DBRs */
+static const struct bootuf2_DBR bootuf2_DBR = {
+ .JMPInstruction = { 0xEB, 0x3C, 0x90 },
+ .OEM = "UF2 UF2 ",
+ .BPB = {
+ .BytesPerSector = CONFIG_BOOTUF2_SECTOR_SIZE,
+ .SectorsPerCluster = CONFIG_BOOTUF2_SECTOR_PER_CLUSTER,
+ .ReservedSectors = CONFIG_BOOTUF2_SECTOR_RESERVED,
+ .NumberOfFAT = CONFIG_BOOTUF2_NUM_OF_FAT,
+ .RootEntries = CONFIG_BOOTUF2_ROOT_ENTRIES,
+ .Sectors = (BOOTUF2_SECTORS(0) > 0xFFFF) ? 0 : BOOTUF2_SECTORS(0),
+ .MediaDescriptor = 0xF8,
+ .SectorsPerFAT = BOOTUF2_SECTORS_PER_FAT(0),
+ .SectorsPerTrack = 1,
+ .Heads = 1,
+ .HiddenSectors = 0,
+ .SectorsOver32MB = (BOOTUF2_SECTORS(0) > 0xFFFF) ? BOOTUF2_SECTORS(0) : 0,
+ .BIOSDrive = 0x80,
+ .Reserved = 0,
+ .ExtendBootSignature = 0x29,
+ .VolumeSerialNumber = 0x00420042,
+ .VolumeLabel = "CHERRYUF2",
+ .FileSystem = "FAT16 ",
+ },
+};
+
+/*!< define mask */
+static uint8_t __attribute__((aligned(4))) bootuf2_mask[BOOTUF2_BLOCKSMAX / 8 + 1] = { 0 };
+
+/*!< define state */
+static struct bootuf2_STATE bootuf2_STATE = {
+ .NumberOfBlock = 0,
+ .NumberOfWritten = 0,
+ .Mask = bootuf2_mask,
+ .Enable = 1,
+};
+
+/*!< define flash cache */
+static uint8_t __attribute__((aligned(4))) bootuf2_disk_cache[CONFIG_BOOTUF2_CACHE_SIZE];
+
+/*!< define flash buff */
+static uint8_t __attribute__((aligned(4))) bootuf2_disk_fbuff[256];
+
+/*!< define erase flag buff */
+static uint8_t __attribute__((aligned(4))) bootuf2_disk_erase[BOOTUF2_DIVCEIL(CONFIG_BOOTUF2_PAGE_COUNTMAX, 8)];
+
+/*!< define disk */
+static struct bootuf2_data bootuf2_disk = {
+ .DBR = &bootuf2_DBR,
+ .STATE = &bootuf2_STATE,
+ .fbuff = bootuf2_disk_fbuff,
+ .erase = bootuf2_disk_erase,
+ .cache = bootuf2_disk_cache,
+ .cache_size = sizeof(bootuf2_disk_cache),
+};
+
+static void fname_copy(char *dst, char const *src, uint16_t len)
+{
+ for (size_t i = 0; i < len; ++i) {
+ if (*src)
+ *dst++ = *src++;
+ else
+ *dst++ = ' ';
+ }
+}
+
+static void fcalculate_cluster(struct bootuf2_data *ctx)
+{
+ /*!< init files cluster */
+ uint16_t cluster_beg = 2;
+ for (int i = 0; i < ARRAY_SIZE(files); i++) {
+ files[i].ClusterBeg = cluster_beg;
+ files[i].ClusterEnd = -1 + cluster_beg +
+ BOOTUF2_DIVCEIL(files[i].FileSize,
+ ctx->DBR->BPB.BytesPerSector *
+ ctx->DBR->BPB.SectorsPerCluster);
+ cluster_beg = files[i].ClusterEnd + 1;
+ }
+}
+
+static int ffind_by_cluster(uint32_t cluster)
+{
+ if (cluster >= 0xFFF0) {
+ return -1;
+ }
+
+ for (uint32_t i = 0; i < ARRAY_SIZE(files); i++) {
+ if ((files[i].ClusterBeg <= cluster) &&
+ (cluster <= files[i].ClusterEnd)) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static bool uf2block_check_address(struct bootuf2_data *ctx,
+ struct bootuf2_BLOCK *uf2)
+{
+ uint32_t beg;
+ uint32_t end;
+
+ beg = uf2->TargetAddress;
+ end = uf2->TargetAddress + uf2->PayloadSize;
+
+ // if ((end >= beg) && (beg >= ctx->offset) &&
+ // (end <= ctx->offset + ctx->size))
+ // {
+ // return true;
+ // }
+
+ return true;
+}
+
+static bool bootuf2block_check_writable(struct bootuf2_STATE *STATE,
+ struct bootuf2_BLOCK *uf2, uint32_t block_max)
+{
+ if (uf2->NumberOfBlock)
+ {
+ if (uf2->BlockIndex < block_max)
+ {
+ uint8_t mask = 1 << (uf2->BlockIndex % 8);
+ uint32_t pos = uf2->BlockIndex / 8;
+
+ if ((STATE->Mask[pos] & mask) == 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static void bootuf2block_state_update(struct bootuf2_STATE *STATE,
+ struct bootuf2_BLOCK *uf2, uint32_t block_max)
+{
+ if (uf2->NumberOfBlock)
+ {
+ if (STATE->NumberOfBlock != uf2->NumberOfBlock)
+ {
+ if ((uf2->NumberOfBlock >= BOOTUF2_BLOCKSMAX) ||
+ STATE->NumberOfBlock)
+ {
+ /*!< uf2 block only can be update once */
+ /*!< this will cause never auto reboot */
+ STATE->NumberOfBlock = 0xffffffff;
+ }
+ else
+ {
+ STATE->NumberOfBlock = uf2->NumberOfBlock;
+ }
+ }
+
+ if (uf2->BlockIndex < block_max)
+ {
+ uint8_t mask = 1 << (uf2->BlockIndex % 8);
+ uint32_t pos = uf2->BlockIndex / 8;
+
+ if ((STATE->Mask[pos] & mask) == 0)
+ {
+ STATE->Mask[pos] |= mask;
+ STATE->NumberOfWritten++;
+ }
+ }
+ }
+
+ USB_LOG_DBG("UF2 block total %d written %d index %d\r\n",
+ uf2->NumberOfBlock, STATE->NumberOfWritten, uf2->BlockIndex);
+}
+
+static bool bootuf2block_state_check(struct bootuf2_STATE *STATE)
+{
+ return (STATE->NumberOfWritten >= STATE->NumberOfBlock) &&
+ STATE->NumberOfBlock;
+}
+
+void bootuf2_init(void)
+{
+ struct bootuf2_data *ctx;
+
+ ctx = &bootuf2_disk;
+
+ fcalculate_cluster(ctx);
+}
+
+int boot2uf2_read_sector(uint32_t start_sector, uint8_t *buff, uint32_t sector_count)
+{
+ struct bootuf2_data *ctx;
+
+ ctx = &bootuf2_disk;
+
+ while (sector_count) {
+ memset(buff, 0, ctx->DBR->BPB.BytesPerSector);
+
+ uint32_t sector_relative = start_sector;
+
+ /*!< DBR sector */
+ if (start_sector == BOOTUF2_SECTOR_DBR_END) {
+ memcpy(buff, ctx->DBR, sizeof(struct bootuf2_DBR));
+ buff[510] = 0x55;
+ buff[511] = 0xaa;
+ }
+ /*!< FAT sector */
+ else if (start_sector < BOOTUF2_SECTOR_FAT_END(ctx->DBR)) {
+ uint16_t *buff16 = (uint16_t *)buff;
+
+ sector_relative -= BOOTUF2_SECTOR_RSVD_END(ctx->DBR);
+
+ /*!< Perform the same operation on all FAT tables */
+ while (sector_relative >= ctx->DBR->BPB.SectorsPerFAT) {
+ sector_relative -= ctx->DBR->BPB.SectorsPerFAT;
+ }
+
+ uint16_t cluster_unused = files[ARRAY_SIZE(files) - 1].ClusterEnd + 1;
+ uint16_t cluster_absolute_first = sector_relative *
+ BOOTUF2_FAT16_PER_SECTOR(ctx->DBR);
+
+ /*!< cluster used link to chain, or unsed */
+ for (uint16_t i = 0, cluster_absolute = cluster_absolute_first;
+ i < BOOTUF2_FAT16_PER_SECTOR(ctx->DBR);
+ i++, cluster_absolute++) {
+ if (cluster_absolute >= cluster_unused)
+ buff16[i] = 0;
+ else
+ buff16[i] = cluster_absolute + 1;
+ }
+
+ /*!< cluster 0 and 1 */
+ if (sector_relative == 0) {
+ buff[0] = ctx->DBR->BPB.MediaDescriptor;
+ buff[1] = 0xff;
+ buff16[1] = 0xffff;
+ }
+
+ /*!< cluster end of file */
+ for (uint32_t i = 0; i < ARRAY_SIZE(files); i++) {
+ uint16_t cluster_file_last = files[i].ClusterEnd;
+
+ if (cluster_file_last >= cluster_absolute_first) {
+ uint16_t idx = cluster_file_last - cluster_absolute_first;
+ if (idx < BOOTUF2_FAT16_PER_SECTOR(ctx->DBR)) {
+ buff16[idx] = 0xffff;
+ }
+ }
+ }
+ }
+ /*!< root entries */
+ else if (start_sector < BOOTUF2_SECTOR_ROOT_END(ctx->DBR)) {
+ sector_relative -= BOOTUF2_SECTOR_FAT_END(ctx->DBR);
+
+ struct bootuf2_ENTRY *ent = (void *)buff;
+ int remain_entries = BOOTUF2_ENTRY_PER_SECTOR(ctx->DBR);
+
+ uint32_t file_index_first;
+
+ /*!< volume label entry */
+ if (sector_relative == 0) {
+ fname_copy(ent->Name, (char const *)ctx->DBR->BPB.VolumeLabel, 11);
+ ent->Attribute = 0x28;
+ ent++;
+ remain_entries--;
+ file_index_first = 0;
+ } else {
+ /*!< -1 to account for volume label in first sector */
+ file_index_first = sector_relative * BOOTUF2_ENTRY_PER_SECTOR(ctx->DBR) - 1;
+ }
+
+ for (uint32_t idx = file_index_first;
+ (remain_entries > 0) && (idx < ARRAY_SIZE(files));
+ idx++, ent++) {
+ const uint32_t cluster_beg = files[idx].ClusterBeg;
+
+ const struct bootuf2_FILE *f = &files[idx];
+
+ if ((0 == f->FileSize) &&
+ (0 != idx)) {
+ continue;
+ }
+
+ fname_copy(ent->Name, f->Name, 11);
+ ent->Attribute = 0x05;
+ ent->CreateTimeTeenth = BOOTUF2_SECONDS_INT % 2 * 100;
+ ent->CreateTime = BOOTUF2_DOS_TIME;
+ ent->CreateDate = BOOTUF2_DOS_DATE;
+ ent->LastAccessDate = BOOTUF2_DOS_DATE;
+ ent->FirstClustH16 = cluster_beg >> 16;
+ ent->UpdateTime = BOOTUF2_DOS_TIME;
+ ent->UpdateDate = BOOTUF2_DOS_DATE;
+ ent->FirstClustL16 = cluster_beg & 0xffff;
+ ent->FileSize = f->FileSize;
+ }
+ }
+ /*!< data */
+ else if (start_sector < BOOTUF2_SECTOR_DATA_END(ctx->DBR)) {
+ sector_relative -= BOOTUF2_SECTOR_ROOT_END(ctx->DBR);
+
+ int fid = ffind_by_cluster(2 + sector_relative / ctx->DBR->BPB.SectorsPerCluster);
+
+ if (fid >= 0) {
+ const struct bootuf2_FILE *f = &files[fid];
+
+ uint32_t sector_relative_file =
+ sector_relative -
+ (files[fid].ClusterBeg - 2) * ctx->DBR->BPB.SectorsPerCluster;
+
+ size_t fcontent_offset = sector_relative_file * ctx->DBR->BPB.BytesPerSector;
+ size_t fcontent_length = f->FileSize;
+
+ if (fcontent_length > fcontent_offset) {
+ const void *src = (void *)((uint8_t *)(f->Content) + fcontent_offset);
+ size_t copy_size = fcontent_length - fcontent_offset;
+
+ if (copy_size > ctx->DBR->BPB.BytesPerSector) {
+ copy_size = ctx->DBR->BPB.BytesPerSector;
+ }
+
+ memcpy(buff, src, copy_size);
+ }
+ }
+ }
+ /*!< unknown sector, ignore */
+
+ start_sector++;
+ sector_count--;
+ buff += ctx->DBR->BPB.BytesPerSector;
+ }
+
+ return 0;
+}
+
+int bootuf2_write_sector(uint32_t start_sector, const uint8_t *buff, uint32_t sector_count)
+{
+ struct bootuf2_data *ctx;
+
+ ctx = &bootuf2_disk;
+
+ while (sector_count) {
+ struct bootuf2_BLOCK *uf2 = (void *)buff;
+
+ if (!((uf2->MagicStart0 == BOOTUF2_MAGIC_START0) &&
+ (uf2->MagicStart1 == BOOTUF2_MAGIC_START1) &&
+ (uf2->MagicEnd == BOOTUF2_MAGIC_END) &&
+ (uf2->Flags & BOOTUF2_FLAG_FAMILID_PRESENT) &&
+ !(uf2->Flags & BOOTUF2_FLAG_NOT_MAIN_FLASH))) {
+ goto next;
+ }
+
+ if (uf2->FamilyID == CONFIG_BOOTUF2_FAMILYID) {
+ if (bootuf2block_check_writable(ctx->STATE, uf2, CONFIG_BOOTUF2_FLASHMAX)) {
+ bootuf2_write_flash(ctx, uf2);
+ bootuf2block_state_update(ctx->STATE, uf2, CONFIG_BOOTUF2_FLASHMAX);
+ } else {
+ USB_LOG_DBG("UF2 block %d already written\r\n",
+ uf2->BlockIndex);
+ }
+ }
+ else {
+ USB_LOG_DBG("UF2 block illegal id %08x\r\n", uf2->FamilyID);
+ }
+
+ next:
+ start_sector++;
+ sector_count--;
+ buff += ctx->DBR->BPB.BytesPerSector;
+ }
+
+ return 0;
+}
+
+uint16_t bootuf2_get_sector_size(void)
+{
+ return bootuf2_disk.DBR->BPB.BytesPerSector;
+}
+
+uint32_t bootuf2_get_sector_count(void)
+{
+ return bootuf2_disk.DBR->BPB.SectorsOver32MB + bootuf2_disk.DBR->BPB.Sectors;
+}
+
+bool bootuf2_is_write_done(void)
+{
+ return bootuf2block_state_check(&bootuf2_disk.STATE);
+}
\ No newline at end of file
diff --git a/demo/bootuf2/bootuf2.h b/demo/bootuf2/bootuf2.h
new file mode 100644
index 0000000..b6a6e16
--- /dev/null
+++ b/demo/bootuf2/bootuf2.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2024, sakumisu
+ * Copyright (c) 2024, Egahp
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef BOOTUF2_H
+#define BOOTUF2_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef __PACKED
+#define __PACKED __attribute__((packed))
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(array) \
+ ((int)((sizeof(array) / sizeof((array)[0]))))
+#endif
+
+struct bootuf2_BLOCK
+{
+ // 32 byte header
+ uint32_t MagicStart0;
+ uint32_t MagicStart1;
+ uint32_t Flags;
+ uint32_t TargetAddress;
+ uint32_t PayloadSize;
+ uint32_t BlockIndex;
+ uint32_t NumberOfBlock;
+ uint32_t FamilyID; // or file_size
+ uint8_t Data[476];
+ uint32_t MagicEnd;
+} __PACKED;
+//BUILD_ASSERT(sizeof(struct bootuf2_BLOCK) == 512, "bootuf2_BLOCK not sector sized");
+
+struct bootuf2_STATE
+{
+ uint32_t NumberOfBlock;
+ uint32_t NumberOfWritten;
+ uint8_t *const Mask;
+ uint8_t Enable;
+};
+
+struct bootuf2_DBR
+{
+ /*!< offset 0 */
+ uint8_t JMPInstruction[3];
+ /*!< offset 3 */
+ uint8_t OEM[8];
+ /*!< offset 11 */
+ struct
+ {
+ uint16_t BytesPerSector;
+ uint8_t SectorsPerCluster;
+ uint16_t ReservedSectors;
+ uint8_t NumberOfFAT;
+ uint16_t RootEntries;
+ uint16_t Sectors;
+ uint8_t MediaDescriptor;
+ uint16_t SectorsPerFAT;
+ uint16_t SectorsPerTrack;
+ uint16_t Heads;
+ uint32_t HiddenSectors;
+ uint32_t SectorsOver32MB;
+ uint8_t BIOSDrive;
+ uint8_t Reserved;
+ uint8_t ExtendBootSignature;
+ uint32_t VolumeSerialNumber;
+ uint8_t VolumeLabel[11];
+ uint8_t FileSystem[8];
+ } __PACKED BPB;
+ /*!< offset 62 */
+ /*!< BootLoader */
+ /*!< offset 511 */
+ /*!< 0x55 0xAA */
+} __PACKED;
+//BUILD_ASSERT(sizeof(struct bootuf2_DBR) == 62, "bootuf2_DBR size must be 62 byte");
+
+struct bootuf2_ENTRY
+{
+ char Name[11];
+ uint8_t Attribute;
+ uint8_t NTReserved;
+ uint8_t CreateTimeTeenth;
+ uint16_t CreateTime;
+ uint16_t CreateDate;
+ uint16_t LastAccessDate;
+ uint16_t FirstClustH16;
+ uint16_t UpdateTime;
+ uint16_t UpdateDate;
+ uint16_t FirstClustL16;
+ uint32_t FileSize;
+} __PACKED;
+//BUILD_ASSERT(sizeof(struct bootuf2_ENTRY) == 32, "bootuf2_ENTRY size must be 32 byte");
+
+struct bootuf2_FILE
+{
+ const char *const Name;
+ const void *const Content;
+ uint32_t FileSize;
+ uint16_t ClusterBeg;
+ uint16_t ClusterEnd;
+};
+
+struct bootuf2_data {
+ const struct bootuf2_DBR *const DBR;
+ struct bootuf2_STATE *const STATE;
+ uint8_t *const fbuff;
+ uint8_t *const erase;
+ size_t page_count;
+ uint8_t *const cache;
+ const size_t cache_size;
+ uint32_t cached_address;
+ size_t cached_bytes;
+};
+
+#define BOOTUF2_DIVCEIL(_v, _d) (((_v) / (_d)) + ((_v) % (_d) ? 1 : 0))
+
+#define BOOTUF2_MAGIC_START0 0x0A324655u
+#define BOOTUF2_MAGIC_START1 0x9E5D5157u
+#define BOOTUF2_MAGIC_SERIAL 0x251B18BDu
+#define BOOTUF2_MAGIC_END 0x0AB16F30u
+
+#define BOOTUF2_FLAG_NOT_MAIN_FLASH 0x00000001u
+#define BOOTUF2_FLAG_FILE_CONTAINER 0x00001000u
+#define BOOTUF2_FLAG_FAMILID_PRESENT 0x00002000u
+#define BOOTUF2_FLAG_MD5_PRESENT 0x00004000u
+
+#define BOOTUF2_CMD_READ 0
+#define BOOTUF2_CMD_SYNC 1
+
+#define BOOTUF2_BLOCKSMAX (((CONFIG_BOOTUF2_FLASHMAX) / 256) + (((CONFIG_BOOTUF2_FLASHMAX) % 256) ? 1 : 0))
+
+#define BOOTUF2_FAMILYID_POSNUM(n) (((CONFIG_BOOTUF2_FAMILYID) / (0x10000000 >> ((n) * 4))) % 0x10)
+#define BOOTUF2_FAMILYID_ARRAY \
+ { \
+ ((BOOTUF2_FAMILYID_POSNUM(0) >= 10) ? BOOTUF2_FAMILYID_POSNUM(0) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(0) + '0'), \
+ ((BOOTUF2_FAMILYID_POSNUM(1) >= 10) ? BOOTUF2_FAMILYID_POSNUM(1) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(1) + '0'), \
+ ((BOOTUF2_FAMILYID_POSNUM(2) >= 10) ? BOOTUF2_FAMILYID_POSNUM(2) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(2) + '0'), \
+ ((BOOTUF2_FAMILYID_POSNUM(3) >= 10) ? BOOTUF2_FAMILYID_POSNUM(3) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(3) + '0'), \
+ ((BOOTUF2_FAMILYID_POSNUM(4) >= 10) ? BOOTUF2_FAMILYID_POSNUM(4) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(4) + '0'), \
+ ((BOOTUF2_FAMILYID_POSNUM(5) >= 10) ? BOOTUF2_FAMILYID_POSNUM(5) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(5) + '0'), \
+ ((BOOTUF2_FAMILYID_POSNUM(6) >= 10) ? BOOTUF2_FAMILYID_POSNUM(6) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(6) + '0'), \
+ ((BOOTUF2_FAMILYID_POSNUM(7) >= 10) ? BOOTUF2_FAMILYID_POSNUM(7) - 10 + 'A' : BOOTUF2_FAMILYID_POSNUM(7) + '0'), \
+ ('I'), \
+ ('D'), \
+ (' '), \
+ ('\0'), \
+ };
+
+#define BOOTUF2_FAT16_PER_SECTOR(pDBR) (pDBR->BPB.BytesPerSector / 2)
+#define BOOTUF2_ENTRY_PER_SECTOR(pDBR) (pDBR->BPB.BytesPerSector / sizeof(struct bootuf2_ENTRY))
+#define BOOTUF2_CLUSTERSMAX (0xFFF0 - 2)
+#define BOOTUF2_SECTOR_DBR_END (0)
+#define BOOTUF2_SECTOR_RSVD_END(pDBR) BOOTUF2_SECTOR_DBR_END + (pDBR->BPB.ReservedSectors)
+#define BOOTUF2_SECTOR_FAT_END(pDBR) BOOTUF2_SECTOR_RSVD_END(pDBR) + (pDBR->BPB.SectorsPerFAT * pDBR->BPB.NumberOfFAT)
+#define BOOTUF2_SECTOR_ROOT_END(pDBR) BOOTUF2_SECTOR_FAT_END(pDBR) + (pDBR->BPB.RootEntries / (pDBR->BPB.BytesPerSector / sizeof(struct bootuf2_ENTRY)))
+#define BOOTUF2_SECTOR_DATA_END(pDBR) (pDBR->BPB.Sectors + pDBR->BPB.SectorsOver32MB)
+
+#define BOOTUF2_SECTORS_PER_FAT(n) \
+ BOOTUF2_DIVCEIL(BOOTUF2_CLUSTERSMAX, (CONFIG_BOOTUF2_SECTOR_SIZE / 2))
+#define BOOTUF2_SECTORS_FOR_ENTRIES(n) \
+ (CONFIG_BOOTUF2_ROOT_ENTRIES / (CONFIG_BOOTUF2_SECTOR_SIZE / sizeof(struct bootuf2_ENTRY)))
+#define BOOTUF2_SECTORS(n) \
+ (CONFIG_BOOTUF2_SECTOR_RESERVED + \
+ CONFIG_BOOTUF2_NUM_OF_FAT * BOOTUF2_SECTORS_PER_FAT(n) + \
+ BOOTUF2_SECTORS_FOR_ENTRIES(n) + \
+ BOOTUF2_CLUSTERSMAX * CONFIG_BOOTUF2_SECTOR_PER_CLUSTER)
+
+#define BOOTUF2_YEAR_INT ( \
+ (__DATE__[7u] - '0') * 1000u + \
+ (__DATE__[8u] - '0') * 100u + \
+ (__DATE__[9u] - '0') * 10u + \
+ (__DATE__[10u] - '0') * 1u)
+
+#define BOOTUF2_MONTH_INT ( \
+ (__DATE__[2u] == 'n' && __DATE__[1u] == 'a') ? 1u /*Jan*/ \
+ : (__DATE__[2u] == 'b') ? 2u /*Feb*/ \
+ : (__DATE__[2u] == 'r' && __DATE__[1u] == 'a') ? 3u /*Mar*/ \
+ : (__DATE__[2u] == 'r') ? 4u /*Apr*/ \
+ : (__DATE__[2u] == 'y') ? 5u /*May*/ \
+ : (__DATE__[2u] == 'n') ? 6u /*Jun*/ \
+ : (__DATE__[2u] == 'l') ? 7u /*Jul*/ \
+ : (__DATE__[2u] == 'g') ? 8u /*Aug*/ \
+ : (__DATE__[2u] == 'p') ? 9u /*Sep*/ \
+ : (__DATE__[2u] == 't') ? 10u /*Oct*/ \
+ : (__DATE__[2u] == 'v') ? 11u /*Nov*/ \
+ : 12u /*Dec*/)
+
+#define BOOTUF2_DAY_INT ( \
+ (__DATE__[4u] == ' ' ? 0 : __DATE__[4u] - '0') * 10u + \
+ (__DATE__[5u] - '0'))
+
+#define BOOTUF2_HOUR_INT ( \
+ (__TIME__[0u] == '?' ? 0 : __TIME__[0u] - '0') * 10u + (__TIME__[1u] == '?' ? 0 : __TIME__[1u] - '0'))
+
+#define BOOTUF2_MINUTE_INT ( \
+ (__TIME__[3u] == '?' ? 0 : __TIME__[3u] - '0') * 10u + (__TIME__[4u] == '?' ? 0 : __TIME__[4u] - '0'))
+
+#define BOOTUF2_SECONDS_INT ( \
+ (__TIME__[6u] == '?' ? 0 : __TIME__[6u] - '0') * 10u + (__TIME__[7u] == '?' ? 0 : __TIME__[7u] - '0'))
+
+#define BOOTUF2_DOS_DATE ( \
+ ((BOOTUF2_YEAR_INT - 1980u) << 9u) | \
+ (BOOTUF2_MONTH_INT << 5u) | \
+ (BOOTUF2_DAY_INT << 0u))
+
+#define BOOTUF2_DOS_TIME ( \
+ (BOOTUF2_HOUR_INT << 11u) | \
+ (BOOTUF2_MINUTE_INT << 5u) | \
+ (BOOTUF2_SECONDS_INT << 0u))
+
+void bootuf2_init(void);
+int boot2uf2_read_sector(uint32_t start_sector, uint8_t *buff, uint32_t sector_count);
+int bootuf2_write_sector(uint32_t start_sector, const uint8_t *buff, uint32_t sector_count);
+uint16_t bootuf2_get_sector_size(void);
+uint32_t bootuf2_get_sector_count(void);
+
+bool bootuf2_is_write_done(void);
+int bootuf2_write_flash(struct bootuf2_data *ctx, struct bootuf2_BLOCK *uf2);
+
+#endif /* BOOTUF2_H */
diff --git a/demo/bootuf2/bootuf2_config.h b/demo/bootuf2/bootuf2_config.h
new file mode 100644
index 0000000..5bc8602
--- /dev/null
+++ b/demo/bootuf2/bootuf2_config.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2024, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef BOOTUF2_CONFIG_H
+#define BOOTUF2_CONFIG_H
+
+#define CONFIG_PRODUCT "CherryUSB"
+#define CONFIG_BOARD "CherryUSB BOARD"
+#define CONFIG_BOOT_UF2_INDEX_URL "https://github.com/cherry-embedded"
+#define CONFIG_BOOT_UF2_JOIN_URL "http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GyH2M5XfWTHQzmZis4ClpgvfdObPrvtk&authKey=LmcLhfno%2BiW51wmgVC%2F8WoYwUXqiclzWDHMU1Jy1d6S8cECJ4Q7bfJ%2FTe67RLakI&noverify=0&group_code=642693751"
+
+#define CONFIG_BOOTUF2_CACHE_SIZE 4096
+#define CONFIG_BOOTUF2_SECTOR_SIZE 512
+#define CONFIG_BOOTUF2_SECTOR_PER_CLUSTER 2
+#define CONFIG_BOOTUF2_SECTOR_RESERVED 1
+#define CONFIG_BOOTUF2_NUM_OF_FAT 2
+#define CONFIG_BOOTUF2_ROOT_ENTRIES 64
+
+#define CONFIG_BOOTUF2_FAMILYID 0xFFFFFFFF
+#define CONFIG_BOOTUF2_FLASHMAX 0x800000
+#define CONFIG_BOOTUF2_PAGE_COUNTMAX 1024
+
+#endif
\ No newline at end of file
diff --git a/demo/bootuf2/cherryuf2.png b/demo/bootuf2/cherryuf2.png
new file mode 100644
index 0000000..aff7ee4
Binary files /dev/null and b/demo/bootuf2/cherryuf2.png differ
diff --git a/demo/bootuf2/msc_bootuf2_template.c b/demo/bootuf2/msc_bootuf2_template.c
new file mode 100644
index 0000000..7bba6f9
--- /dev/null
+++ b/demo/bootuf2/msc_bootuf2_template.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2024, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbd_core.h"
+#include "usbd_msc.h"
+#include "bootuf2.h"
+
+#define MSC_IN_EP 0x81
+#define MSC_OUT_EP 0x02
+
+#define USBD_VID 0xFFFF
+#define USBD_PID 0xFFFF
+#define USBD_MAX_POWER 100
+#define USBD_LANGID_STRING 1033
+
+#define USB_CONFIG_SIZE (9 + MSC_DESCRIPTOR_LEN)
+
+#ifdef CONFIG_USB_HS
+#define MSC_MAX_MPS 512
+#else
+#define MSC_MAX_MPS 64
+#endif
+
+const uint8_t msc_bootuf2_descriptor[] = {
+ USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, USBD_VID, USBD_PID, 0x0200, 0x01),
+ USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ MSC_DESCRIPTOR_INIT(0x00, MSC_OUT_EP, MSC_IN_EP, MSC_MAX_MPS, 0x02),
+ ///////////////////////////////////////
+ /// string0 descriptor
+ ///////////////////////////////////////
+ USB_LANGID_INIT(USBD_LANGID_STRING),
+ ///////////////////////////////////////
+ /// string1 descriptor
+ ///////////////////////////////////////
+ 0x14, /* bLength */
+ USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
+ 'C', 0x00, /* wcChar0 */
+ 'h', 0x00, /* wcChar1 */
+ 'e', 0x00, /* wcChar2 */
+ 'r', 0x00, /* wcChar3 */
+ 'r', 0x00, /* wcChar4 */
+ 'y', 0x00, /* wcChar5 */
+ 'U', 0x00, /* wcChar6 */
+ 'S', 0x00, /* wcChar7 */
+ 'B', 0x00, /* wcChar8 */
+ ///////////////////////////////////////
+ /// string2 descriptor
+ ///////////////////////////////////////
+ 0x26, /* bLength */
+ USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
+ 'C', 0x00, /* wcChar0 */
+ 'h', 0x00, /* wcChar1 */
+ 'e', 0x00, /* wcChar2 */
+ 'r', 0x00, /* wcChar3 */
+ 'r', 0x00, /* wcChar4 */
+ 'y', 0x00, /* wcChar5 */
+ 'U', 0x00, /* wcChar6 */
+ 'S', 0x00, /* wcChar7 */
+ 'B', 0x00, /* wcChar8 */
+ ' ', 0x00, /* wcChar9 */
+ 'U', 0x00, /* wcChar10 */
+ 'F', 0x00, /* wcChar11 */
+ '2', 0x00, /* wcChar12 */
+ ' ', 0x00, /* wcChar13 */
+ 'D', 0x00, /* wcChar14 */
+ 'E', 0x00, /* wcChar15 */
+ 'M', 0x00, /* wcChar16 */
+ 'O', 0x00, /* wcChar17 */
+ ///////////////////////////////////////
+ /// string3 descriptor
+ ///////////////////////////////////////
+ 0x16, /* bLength */
+ USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
+ '2', 0x00, /* wcChar0 */
+ '0', 0x00, /* wcChar1 */
+ '2', 0x00, /* wcChar2 */
+ '2', 0x00, /* wcChar3 */
+ '1', 0x00, /* wcChar4 */
+ '2', 0x00, /* wcChar5 */
+ '3', 0x00, /* wcChar6 */
+ '4', 0x00, /* wcChar7 */
+ '5', 0x00, /* wcChar8 */
+ '6', 0x00, /* wcChar9 */
+#ifdef CONFIG_USB_HS
+ ///////////////////////////////////////
+ /// device qualifier descriptor
+ ///////////////////////////////////////
+ 0x0a,
+ USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x40,
+ 0x01,
+ 0x00,
+#endif
+ 0x00
+};
+
+static void usbd_event_handler(uint8_t busid, uint8_t event)
+{
+ switch (event) {
+ case USBD_EVENT_RESET:
+ break;
+ case USBD_EVENT_CONNECTED:
+ break;
+ case USBD_EVENT_DISCONNECTED:
+ break;
+ case USBD_EVENT_RESUME:
+ break;
+ case USBD_EVENT_SUSPEND:
+ break;
+ case USBD_EVENT_CONFIGURED:
+ break;
+ case USBD_EVENT_SET_REMOTE_WAKEUP:
+ break;
+ case USBD_EVENT_CLR_REMOTE_WAKEUP:
+ break;
+
+ default:
+ break;
+ }
+}
+
+void usbd_msc_get_cap(uint8_t busid, uint8_t lun, uint32_t *block_num, uint32_t *block_size)
+{
+ *block_num = bootuf2_get_sector_count();
+ *block_size = bootuf2_get_sector_size();
+
+ USB_LOG_INFO("sector count:%d, sector size:%d\n", *block_num, *block_size);
+}
+int usbd_msc_sector_read(uint8_t busid, uint8_t lun, uint32_t sector, uint8_t *buffer, uint32_t length)
+{
+ boot2uf2_read_sector(sector, buffer, length / bootuf2_get_sector_size());
+ return 0;
+}
+
+int usbd_msc_sector_write(uint8_t busid, uint8_t lun, uint32_t sector, uint8_t *buffer, uint32_t length)
+{
+ bootuf2_write_sector(sector, buffer, length / bootuf2_get_sector_size());
+ return 0;
+}
+
+static struct usbd_interface intf0;
+
+void msc_bootuf2_init(uint8_t busid, uint32_t reg_base)
+{
+ bootuf2_init();
+
+ usbd_desc_register(busid, msc_bootuf2_descriptor);
+ usbd_add_interface(busid, usbd_msc_init_intf(busid, &intf0, MSC_OUT_EP, MSC_IN_EP));
+
+ usbd_initialize(busid, reg_base, usbd_event_handler);
+}
+
+int bootuf2_write_flash(struct bootuf2_data *ctx, struct bootuf2_BLOCK *uf2)
+{
+ return 0;
+}
diff --git a/demo/msc_ram_template.c b/demo/msc_ram_template.c
index 933cd88..99802b9 100644
--- a/demo/msc_ram_template.c
+++ b/demo/msc_ram_template.c
@@ -154,7 +154,7 @@ int usbd_msc_sector_write(uint8_t busid, uint8_t lun, uint32_t sector, uint8_t *
return 0;
}
-struct usbd_interface intf0;
+static struct usbd_interface intf0;
void msc_ram_init(uint8_t busid, uint32_t reg_base)
{
diff --git a/tools/uf2/uf2conv.py b/tools/uf2/uf2conv.py
new file mode 100644
index 0000000..53c5e8b
--- /dev/null
+++ b/tools/uf2/uf2conv.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python3
+import sys
+import struct
+import subprocess
+import re
+import os
+import os.path
+import argparse
+import json
+from time import sleep
+
+
+UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
+UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
+UF2_MAGIC_END = 0x0AB16F30 # Ditto
+
+INFO_FILE = "/INFO_UF2.TXT"
+
+appstartaddr = 0x2000
+familyid = 0x0
+
+
+def is_uf2(buf):
+ w = struct.unpack(" 476:
+ assert False, "Invalid UF2 data size at " + ptr
+ newaddr = hd[3]
+ if (hd[2] & 0x2000) and (currfamilyid == None):
+ currfamilyid = hd[7]
+ if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid):
+ currfamilyid = hd[7]
+ curraddr = newaddr
+ if familyid == 0x0 or familyid == hd[7]:
+ appstartaddr = newaddr
+ padding = newaddr - curraddr
+ if padding < 0:
+ assert False, "Block out of order at " + ptr
+ if padding > 10*1024*1024:
+ assert False, "More than 10M of padding needed at " + ptr
+ if padding % 4 != 0:
+ assert False, "Non-word padding size at " + ptr
+ while padding > 0:
+ padding -= 4
+ outp.append(b"\x00\x00\x00\x00")
+ if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]):
+ outp.append(block[32 : 32 + datalen])
+ curraddr = newaddr + datalen
+ if hd[2] & 0x2000:
+ if hd[7] in families_found.keys():
+ if families_found[hd[7]] > newaddr:
+ families_found[hd[7]] = newaddr
+ else:
+ families_found[hd[7]] = newaddr
+ if prev_flag == None:
+ prev_flag = hd[2]
+ if prev_flag != hd[2]:
+ all_flags_same = False
+ if blockno == (numblocks - 1):
+ print("--- UF2 File Header Info ---")
+ families = load_families()
+ for family_hex in families_found.keys():
+ family_short_name = ""
+ for name, value in families.items():
+ if value == family_hex:
+ family_short_name = name
+ print("Family ID is {:s}, hex value is 0x{:08x}".format(family_short_name,family_hex))
+ print("Target Address is 0x{:08x}".format(families_found[family_hex]))
+ if all_flags_same:
+ print("All block flag values consistent, 0x{:04x}".format(hd[2]))
+ else:
+ print("Flags were not all the same")
+ print("----------------------------")
+ if len(families_found) > 1 and familyid == 0x0:
+ outp = []
+ appstartaddr = 0x0
+ return b"".join(outp)
+
+def convert_to_carray(file_content):
+ outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
+ outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
+ for i in range(len(file_content)):
+ if i % 16 == 0:
+ outp += "\n"
+ outp += "0x%02x, " % file_content[i]
+ outp += "\n};\n"
+ return bytes(outp, "utf-8")
+
+def convert_to_uf2(file_content):
+ global familyid
+ datapadding = b""
+ while len(datapadding) < 512 - 256 - 32 - 4:
+ datapadding += b"\x00\x00\x00\x00"
+ numblocks = (len(file_content) + 255) // 256
+ outp = []
+ for blockno in range(numblocks):
+ ptr = 256 * blockno
+ chunk = file_content[ptr:ptr + 256]
+ flags = 0x0
+ if familyid:
+ flags |= 0x2000
+ hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT":
+ drives.append(words[0])
+ else:
+ searchpaths = ["/media"]
+ if sys.platform == "darwin":
+ searchpaths = ["/Volumes"]
+ elif sys.platform == "linux":
+ searchpaths += ["/media/" + os.environ["USER"], '/run/media/' + os.environ["USER"]]
+
+ for rootpath in searchpaths:
+ if os.path.isdir(rootpath):
+ for d in os.listdir(rootpath):
+ if os.path.isdir(rootpath):
+ drives.append(os.path.join(rootpath, d))
+
+
+ def has_info(d):
+ try:
+ return os.path.isfile(d + INFO_FILE)
+ except:
+ return False
+
+ return list(filter(has_info, drives))
+
+
+def board_id(path):
+ with open(path + INFO_FILE, mode='r') as file:
+ file_content = file.read()
+ return re.search(r"Board-ID: ([^\r\n]*)", file_content).group(1)
+
+
+def list_drives():
+ for d in get_drives():
+ print(d, board_id(d))
+
+
+def write_file(name, buf):
+ with open(name, "wb") as f:
+ f.write(buf)
+ print("Wrote %d bytes to %s" % (len(buf), name))
+
+
+def load_families():
+ # The expectation is that the `uf2families.json` file is in the same
+ # directory as this script. Make a path that works using `__file__`
+ # which contains the full path to this script.
+ filename = "uf2families.json"
+ pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
+ with open(pathname) as f:
+ raw_families = json.load(f)
+
+ families = {}
+ for family in raw_families:
+ families[family["short_name"]] = int(family["id"], 0)
+
+ return families
+
+
+def main():
+ global appstartaddr, familyid
+ def error(msg):
+ print(msg, file=sys.stderr)
+ sys.exit(1)
+ parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
+ parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
+ help='input file (HEX, BIN or UF2)')
+ parser.add_argument('-b', '--base', dest='base', type=str,
+ default="0x2000",
+ help='set base address of application for BIN format (default: 0x2000)')
+ parser.add_argument('-f', '--family', dest='family', type=str,
+ default="0x0",
+ help='specify familyID - number or name (default: 0x0)')
+ parser.add_argument('-o', '--output', metavar="FILE", dest='output', type=str,
+ help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
+ parser.add_argument('-d', '--device', dest="device_path",
+ help='select a device path to flash')
+ parser.add_argument('-l', '--list', action='store_true',
+ help='list connected devices')
+ parser.add_argument('-c', '--convert', action='store_true',
+ help='do not flash, just convert')
+ parser.add_argument('-D', '--deploy', action='store_true',
+ help='just flash, do not convert')
+ parser.add_argument('-w', '--wait', action='store_true',
+ help='wait for device to flash')
+ parser.add_argument('-C', '--carray', action='store_true',
+ help='convert binary file to a C array, not UF2')
+ parser.add_argument('-i', '--info', action='store_true',
+ help='display header information from UF2, do not convert')
+ args = parser.parse_args()
+ appstartaddr = int(args.base, 0)
+
+ families = load_families()
+
+ if args.family.upper() in families:
+ familyid = families[args.family.upper()]
+ else:
+ try:
+ familyid = int(args.family, 0)
+ except ValueError:
+ error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
+
+ if args.list:
+ list_drives()
+ else:
+ if not args.input:
+ error("Need input file")
+ with open(args.input, mode='rb') as f:
+ inpbuf = f.read()
+ from_uf2 = is_uf2(inpbuf)
+ ext = "uf2"
+ if args.deploy:
+ outbuf = inpbuf
+ elif from_uf2 and not args.info:
+ outbuf = convert_from_uf2(inpbuf)
+ ext = "bin"
+ elif from_uf2 and args.info:
+ outbuf = ""
+ convert_from_uf2(inpbuf)
+ elif is_hex(inpbuf):
+ outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
+ elif args.carray:
+ outbuf = convert_to_carray(inpbuf)
+ ext = "h"
+ else:
+ outbuf = convert_to_uf2(inpbuf)
+ if not args.deploy and not args.info:
+ print("Converted to %s, output size: %d, start address: 0x%x" %
+ (ext, len(outbuf), appstartaddr))
+ if args.convert or ext != "uf2":
+ if args.output == None:
+ args.output = "flash." + ext
+ if args.output:
+ write_file(args.output, outbuf)
+ if ext == "uf2" and not args.convert and not args.info:
+ drives = get_drives()
+ if len(drives) == 0:
+ if args.wait:
+ print("Waiting for drive to deploy...")
+ while len(drives) == 0:
+ sleep(0.1)
+ drives = get_drives()
+ elif not args.output:
+ error("No drive to deploy.")
+ for d in drives:
+ print("Flashing %s (%s)" % (d, board_id(d)))
+ write_file(d + "/NEW.UF2", outbuf)
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file