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