/** * Copyright (c) 2011-2022 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #define DBG_FILE "ExFatFormatter.cpp" #include "../common/DebugMacros.h" #include "../common/upcase.h" #include "ExFatLib.h" //------------------------------------------------------------------------------ // Formatter assumes 512 byte sectors. const uint32_t BOOT_BACKUP_OFFSET = 12; const uint16_t BYTES_PER_SECTOR = 512; const uint16_t SECTOR_MASK = BYTES_PER_SECTOR - 1; const uint8_t BYTES_PER_SECTOR_SHIFT = 9; const uint16_t MINIMUM_UPCASE_SKIP = 512; const uint32_t BITMAP_CLUSTER = 2; const uint32_t UPCASE_CLUSTER = 3; const uint32_t ROOT_CLUSTER = 4; //------------------------------------------------------------------------------ #define PRINT_FORMAT_PROGRESS 1 #if !PRINT_FORMAT_PROGRESS #define writeMsg(pr, str) #elif defined(__AVR__) #define writeMsg(pr, str) \ if (pr) pr->print(F(str)) #else // PRINT_FORMAT_PROGRESS #define writeMsg(pr, str) \ if (pr) pr->write(str) #endif // PRINT_FORMAT_PROGRESS //------------------------------------------------------------------------------ bool ExFatFormatter::format(FsBlockDevice* dev, uint8_t* secBuf, print_t* pr) { #if !PRINT_FORMAT_PROGRESS (void)pr; #endif // !PRINT_FORMAT_PROGRESS MbrSector_t* mbr; ExFatPbs_t* pbs; DirUpcase_t* dup; DirBitmap_t* dbm; DirLabel_t* label; uint32_t bitmapSize; uint32_t checksum = 0; uint32_t clusterCount; uint32_t clusterHeapOffset; uint32_t fatLength; uint32_t fatOffset; uint32_t m; uint32_t ns; uint32_t partitionOffset; uint32_t sector; uint32_t sectorsPerCluster; uint32_t volumeLength; uint32_t sectorCount; uint8_t sectorsPerClusterShift; uint8_t vs; m_dev = dev; m_secBuf = secBuf; sectorCount = dev->sectorCount(); // Min size is 512 MB if (sectorCount < 0X100000) { writeMsg(pr, "Device is too small\r\n"); DBG_FAIL_MACRO; goto fail; } // Determine partition layout. for (m = 1, vs = 0; m && sectorCount > m; m <<= 1, vs++) { } sectorsPerClusterShift = vs < 29 ? 8 : (vs - 11) / 2; sectorsPerCluster = 1UL << sectorsPerClusterShift; fatLength = 1UL << (vs < 27 ? 13 : (vs + 1) / 2); fatOffset = fatLength; partitionOffset = 2 * fatLength; clusterHeapOffset = 2 * fatLength; clusterCount = (sectorCount - 4 * fatLength) >> sectorsPerClusterShift; volumeLength = clusterHeapOffset + (clusterCount << sectorsPerClusterShift); // make Master Boot Record. Use fake CHS. memset(secBuf, 0, BYTES_PER_SECTOR); mbr = reinterpret_cast(secBuf); mbr->part->beginCHS[0] = 1; mbr->part->beginCHS[1] = 1; mbr->part->beginCHS[2] = 0; mbr->part->type = 7; mbr->part->endCHS[0] = 0XFE; mbr->part->endCHS[1] = 0XFF; mbr->part->endCHS[2] = 0XFF; setLe32(mbr->part->relativeSectors, partitionOffset); setLe32(mbr->part->totalSectors, volumeLength); setLe16(mbr->signature, MBR_SIGNATURE); if (!dev->writeSector(0, secBuf)) { DBG_FAIL_MACRO; goto fail; } // Partition Boot sector. memset(secBuf, 0, BYTES_PER_SECTOR); pbs = reinterpret_cast(secBuf); pbs->jmpInstruction[0] = 0XEB; pbs->jmpInstruction[1] = 0X76; pbs->jmpInstruction[2] = 0X90; pbs->oemName[0] = 'E'; pbs->oemName[1] = 'X'; pbs->oemName[2] = 'F'; pbs->oemName[3] = 'A'; pbs->oemName[4] = 'T'; pbs->oemName[5] = ' '; pbs->oemName[6] = ' '; pbs->oemName[7] = ' '; setLe64(pbs->bpb.partitionOffset, partitionOffset); setLe64(pbs->bpb.volumeLength, volumeLength); setLe32(pbs->bpb.fatOffset, fatOffset); setLe32(pbs->bpb.fatLength, fatLength); setLe32(pbs->bpb.clusterHeapOffset, clusterHeapOffset); setLe32(pbs->bpb.clusterCount, clusterCount); setLe32(pbs->bpb.rootDirectoryCluster, ROOT_CLUSTER); setLe32(pbs->bpb.volumeSerialNumber, sectorCount); setLe16(pbs->bpb.fileSystemRevision, 0X100); setLe16(pbs->bpb.volumeFlags, 0); pbs->bpb.bytesPerSectorShift = BYTES_PER_SECTOR_SHIFT; pbs->bpb.sectorsPerClusterShift = sectorsPerClusterShift; pbs->bpb.numberOfFats = 1; pbs->bpb.driveSelect = 0X80; pbs->bpb.percentInUse = 0; // Fill boot code like official SDFormatter. for (size_t i = 0; i < sizeof(pbs->bootCode); i++) { pbs->bootCode[i] = 0XF4; } setLe16(pbs->signature, PBR_SIGNATURE); for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { if (i == offsetof(ExFatPbs_t, bpb.volumeFlags[0]) || i == offsetof(ExFatPbs_t, bpb.volumeFlags[1]) || i == offsetof(ExFatPbs_t, bpb.percentInUse)) { continue; } checksum = exFatChecksum(checksum, secBuf[i]); } sector = partitionOffset; if (!dev->writeSector(sector, secBuf) || !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { DBG_FAIL_MACRO; goto fail; } sector++; // Write eight Extended Boot Sectors. memset(secBuf, 0, BYTES_PER_SECTOR); setLe16(pbs->signature, PBR_SIGNATURE); for (int j = 0; j < 8; j++) { for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { checksum = exFatChecksum(checksum, secBuf[i]); } if (!dev->writeSector(sector, secBuf) || !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { DBG_FAIL_MACRO; goto fail; } sector++; } // Write OEM Parameter Sector and reserved sector. memset(secBuf, 0, BYTES_PER_SECTOR); for (int j = 0; j < 2; j++) { for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { checksum = exFatChecksum(checksum, secBuf[i]); } if (!dev->writeSector(sector, secBuf) || !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { DBG_FAIL_MACRO; goto fail; } sector++; } // Write Boot CheckSum Sector. for (size_t i = 0; i < BYTES_PER_SECTOR; i += 4) { setLe32(secBuf + i, checksum); } if (!dev->writeSector(sector, secBuf) || !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { DBG_FAIL_MACRO; goto fail; } // Initialize FAT. writeMsg(pr, "Writing FAT "); sector = partitionOffset + fatOffset; ns = ((clusterCount + 2) * 4 + BYTES_PER_SECTOR - 1) / BYTES_PER_SECTOR; memset(secBuf, 0, BYTES_PER_SECTOR); // Allocate two reserved clusters, bitmap, upcase, and root clusters. secBuf[0] = 0XF8; for (size_t i = 1; i < 20; i++) { secBuf[i] = 0XFF; } for (uint32_t i = 0; i < ns; i++) { if (i % (ns / 32) == 0) { writeMsg(pr, "."); } if (!dev->writeSector(sector + i, secBuf)) { DBG_FAIL_MACRO; goto fail; } if (i == 0) { memset(secBuf, 0, BYTES_PER_SECTOR); } } writeMsg(pr, "\r\n"); // Write cluster two, bitmap. sector = partitionOffset + clusterHeapOffset; bitmapSize = (clusterCount + 7) / 8; ns = (bitmapSize + BYTES_PER_SECTOR - 1) / BYTES_PER_SECTOR; if (ns > sectorsPerCluster) { DBG_FAIL_MACRO; goto fail; } memset(secBuf, 0, BYTES_PER_SECTOR); // Allocate clusters for bitmap, upcase, and root. secBuf[0] = 0X7; for (uint32_t i = 0; i < ns; i++) { if (!dev->writeSector(sector + i, secBuf)) { DBG_FAIL_MACRO; goto fail; } if (i == 0) { secBuf[0] = 0; } } // Write cluster three, upcase table. writeMsg(pr, "Writing upcase table\r\n"); if (!writeUpcase(partitionOffset + clusterHeapOffset + sectorsPerCluster)) { DBG_FAIL_MACRO; goto fail; } if (m_upcaseSize > BYTES_PER_SECTOR * sectorsPerCluster) { DBG_FAIL_MACRO; goto fail; } // Initialize first sector of root. writeMsg(pr, "Writing root\r\n"); ns = sectorsPerCluster; sector = partitionOffset + clusterHeapOffset + 2 * sectorsPerCluster; memset(secBuf, 0, BYTES_PER_SECTOR); // Unused Label entry. label = reinterpret_cast(secBuf); label->type = EXFAT_TYPE_LABEL & 0X7F; // bitmap directory entry. dbm = reinterpret_cast(secBuf + 32); dbm->type = EXFAT_TYPE_BITMAP; setLe32(dbm->firstCluster, BITMAP_CLUSTER); setLe64(dbm->size, bitmapSize); // upcase directory entry. dup = reinterpret_cast(secBuf + 64); dup->type = EXFAT_TYPE_UPCASE; setLe32(dup->checksum, m_upcaseChecksum); setLe32(dup->firstCluster, UPCASE_CLUSTER); setLe64(dup->size, m_upcaseSize); // Write root, cluster four. for (uint32_t i = 0; i < ns; i++) { if (!dev->writeSector(sector + i, secBuf)) { DBG_FAIL_MACRO; goto fail; } if (i == 0) { memset(secBuf, 0, BYTES_PER_SECTOR); } } writeMsg(pr, "Format done\r\n"); return true; fail: writeMsg(pr, "Format failed\r\n"); return false; } //------------------------------------------------------------------------------ bool ExFatFormatter::syncUpcase() { uint16_t index = m_upcaseSize & SECTOR_MASK; if (!index) { return true; } for (size_t i = index; i < BYTES_PER_SECTOR; i++) { m_secBuf[i] = 0; } return m_dev->writeSector(m_upcaseSector, m_secBuf); } //------------------------------------------------------------------------------ bool ExFatFormatter::writeUpcaseByte(uint8_t b) { uint16_t index = m_upcaseSize & SECTOR_MASK; m_secBuf[index] = b; m_upcaseChecksum = exFatChecksum(m_upcaseChecksum, b); m_upcaseSize++; if (index == SECTOR_MASK) { return m_dev->writeSector(m_upcaseSector++, m_secBuf); } return true; } //------------------------------------------------------------------------------ bool ExFatFormatter::writeUpcaseUnicode(uint16_t unicode) { return writeUpcaseByte(unicode) && writeUpcaseByte(unicode >> 8); } //------------------------------------------------------------------------------ bool ExFatFormatter::writeUpcase(uint32_t sector) { uint32_t n; uint32_t ns; uint32_t ch = 0; uint16_t uc; m_upcaseSize = 0; m_upcaseChecksum = 0; m_upcaseSector = sector; while (ch < 0X10000) { uc = toUpcase(ch); if (uc != ch) { if (!writeUpcaseUnicode(uc)) { DBG_FAIL_MACRO; goto fail; } ch++; } else { for (n = ch + 1; n < 0X10000 && n == toUpcase(n); n++) { } ns = n - ch; if (ns >= MINIMUM_UPCASE_SKIP) { if (!writeUpcaseUnicode(0XFFFF) || !writeUpcaseUnicode(ns)) { DBG_FAIL_MACRO; goto fail; } ch = n; } else { while (ch < n) { if (!writeUpcaseUnicode(ch++)) { DBG_FAIL_MACRO; goto fail; } } } } } if (!syncUpcase()) { DBG_FAIL_MACRO; goto fail; } return true; fail: return false; }