feat(demo): add uf2 demo

This commit is contained in:
sakumisu 2024-07-23 22:37:27 +08:00
parent 4c4ee7f003
commit b399ff63ce
9 changed files with 1210 additions and 1 deletions

View File

@ -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):

View File

@ -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
demo/bootuf2/bootuf2.c Normal file
View File

@ -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
demo/bootuf2/bootuf2.h Normal file
View File

@ -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 */

View File

@ -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 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -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;
}

View File

@ -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
tools/uf2/uf2conv.py Normal file
View File

@ -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()