Explorar el Código

feat(demo): add uf2 demo

sakumisu hace 1 año
padre
commit
b399ff63ce

+ 1 - 0
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):

+ 1 - 0
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):

+ 430 - 0
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[] = {
+    "<!doctype html>\n"
+    "<html>"
+    "<body>"
+    "<script>\n"
+    "location.replace(\"" CONFIG_BOOT_UF2_INDEX_URL "\");\n"
+    "</script>"
+    "</body>"
+    "</html>\n"
+};
+
+const char file_JOIN[] = {
+    "<!doctype html>\n"
+    "<html>"
+    "<body>"
+    "<script>\n"
+    "location.replace(\"" CONFIG_BOOT_UF2_JOIN_URL "\");\n"
+    "</script>"
+    "</body>"
+    "</html>\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);
+}

+ 228 - 0
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 <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <bootuf2_config.h>
+
+#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 */

+ 25 - 0
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

BIN
demo/bootuf2/cherryuf2.png


+ 163 - 0
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;
+}

+ 1 - 1
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)
 {

+ 361 - 0
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("<II", buf[0:8])
+    return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
+
+def is_hex(buf):
+    try:
+        w = buf[0:30].decode("utf-8")
+    except UnicodeDecodeError:
+        return False
+    if w[0] == ':' and re.match(rb"^[:0-9a-fA-F\r\n]+$", buf):
+        return True
+    return False
+
+def convert_from_uf2(buf):
+    global appstartaddr
+    global familyid
+    numblocks = len(buf) // 512
+    curraddr = None
+    currfamilyid = None
+    families_found = {}
+    prev_flag = None
+    all_flags_same = True
+    outp = []
+    for blockno in range(numblocks):
+        ptr = blockno * 512
+        block = buf[ptr:ptr + 512]
+        hd = struct.unpack(b"<IIIIIIII", block[0:32])
+        if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
+            print("Skipping block at " + ptr + "; bad magic")
+            continue
+        if hd[2] & 1:
+            # NO-flash flag set; skip block
+            continue
+        datalen = hd[4]
+        if datalen > 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"<IIIIIIII",
+            UF2_MAGIC_START0, UF2_MAGIC_START1,
+            flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
+        while len(chunk) < 256:
+            chunk += b"\x00"
+        block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
+        assert len(block) == 512
+        outp.append(block)
+    return b"".join(outp)
+
+class Block:
+    def __init__(self, addr):
+        self.addr = addr
+        self.bytes = bytearray(256)
+
+    def encode(self, blockno, numblocks):
+        global familyid
+        flags = 0x0
+        if familyid:
+            flags |= 0x2000
+        hd = struct.pack("<IIIIIIII",
+            UF2_MAGIC_START0, UF2_MAGIC_START1,
+            flags, self.addr, 256, blockno, numblocks, familyid)
+        hd += self.bytes[0:256]
+        while len(hd) < 512 - 4:
+            hd += b"\x00"
+        hd += struct.pack("<I", UF2_MAGIC_END)
+        return hd
+
+def convert_from_hex_to_uf2(buf):
+    global appstartaddr
+    appstartaddr = None
+    upper = 0
+    currblock = None
+    blocks = []
+    for line in buf.split('\n'):
+        if line[0] != ":":
+            continue
+        i = 1
+        rec = []
+        while i < len(line) - 1:
+            rec.append(int(line[i:i+2], 16))
+            i += 2
+        tp = rec[3]
+        if tp == 4:
+            upper = ((rec[4] << 8) | rec[5]) << 16
+        elif tp == 2:
+            upper = ((rec[4] << 8) | rec[5]) << 4
+        elif tp == 1:
+            break
+        elif tp == 0:
+            addr = upper + ((rec[1] << 8) | rec[2])
+            if appstartaddr == None:
+                appstartaddr = addr
+            i = 4
+            while i < len(rec) - 1:
+                if not currblock or currblock.addr & ~0xff != addr & ~0xff:
+                    currblock = Block(addr & ~0xff)
+                    blocks.append(currblock)
+                currblock.bytes[addr & 0xff] = rec[i]
+                addr += 1
+                i += 1
+    numblocks = len(blocks)
+    resfile = b""
+    for i in range(0, numblocks):
+        resfile += blocks[i].encode(i, numblocks)
+    return resfile
+
+def to_str(b):
+    return b.decode("utf-8")
+
+def get_drives():
+    drives = []
+    if sys.platform == "win32":
+        r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
+                                     "get", "DeviceID,", "VolumeName,",
+                                     "FileSystem,", "DriveType"])
+        for line in to_str(r).split('\n'):
+            words = re.split(r'\s+', line)
+            if len(words) >= 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()