From b399ff63ce11c38e09618aab518594780a4e0858 Mon Sep 17 00:00:00 2001 From: sakumisu <1203593632@qq.com> Date: Tue, 23 Jul 2024 22:37:27 +0800 Subject: [PATCH] feat(demo): add uf2 demo --- README.md | 1 + README_zh.md | 1 + demo/bootuf2/bootuf2.c | 430 ++++++++++++++++++++++++++++ demo/bootuf2/bootuf2.h | 228 +++++++++++++++ demo/bootuf2/bootuf2_config.h | 25 ++ demo/bootuf2/cherryuf2.png | Bin 0 -> 19847 bytes demo/bootuf2/msc_bootuf2_template.c | 163 +++++++++++ demo/msc_ram_template.c | 2 +- tools/uf2/uf2conv.py | 361 +++++++++++++++++++++++ 9 files changed, 1210 insertions(+), 1 deletion(-) create mode 100644 demo/bootuf2/bootuf2.c create mode 100644 demo/bootuf2/bootuf2.h create mode 100644 demo/bootuf2/bootuf2_config.h create mode 100644 demo/bootuf2/cherryuf2.png create mode 100644 demo/bootuf2/msc_bootuf2_template.c create mode 100644 tools/uf2/uf2conv.py 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 0000000000000000000000000000000000000000..aff7ee4df875678cdcdf79d2485fc35fdaabffee GIT binary patch literal 19847 zcmdpeWmH^Emo9{a1PBlyxH~isAvlE4xVyUtcZURb*Wm8%9$bUFyEP7tH{4F%@0*#m z=FY79`_>N@tGcSrsl9hqJ^R_aPKcb0C^8}*A`A=+vbY#f0R{$69{T^~J9y~dvRzph z7#I>5aiE}*Yud>wlB*IpUEuka;`_n3!;)mAjU_C#Fg&R(cACDWf)Bo7UoO68O5d|1 z*N=}QfG?Br8kfPOt9P|iM#l|E(!`&iXEm^jOIfOlRRL;D2$TSo>^U+DU|aTg*tnFJ z+bcc?ld&`|2NRj(mG#Qcv7^mirhB8Kw_Y7d7lizm=I`>n&b!`nf@c)pw&DOGk^9_~ zJq||KTMn7to>Czq8R1=wFlc|uq;2;HVc? t3LZ&l3O_ws*hfJDr|zcpxXzy7a{ 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 I&MBb@0Mz5reE 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