diff --git a/src/libs/SDFS/library.properties b/src/libs/SDFS/library.properties new file mode 100644 index 0000000..88eb22a --- /dev/null +++ b/src/libs/SDFS/library.properties @@ -0,0 +1,10 @@ +name=SDFS +version=0.1.0 +author=Earle F. Philhower, III +maintainer=Earle F. Philhower, III +sentence=ESP8266 FS filesystem for use on SD cards using Bill Greiman's amazing FAT16/FAT32 Arduino library. +paragraph=ESP8266 FS filesystem for use on SD cards using Bill Greiman's amazing FAT16/FAT32 Arduino library. +category=Data Storage +url=https://github.com/esp8266/Arduino +architectures=esp8266 +dot_a_linkage=true diff --git a/src/libs/SDFS/src/SDFS.cpp b/src/libs/SDFS/src/SDFS.cpp new file mode 100644 index 0000000..de7832f --- /dev/null +++ b/src/libs/SDFS/src/SDFS.cpp @@ -0,0 +1,154 @@ +/* + SDFS.cpp - file system wrapper for SdFat + Copyright (c) 2019 Earle F. Philhower, III. All rights reserved. + + Based on spiffs_api.cpp which is: + | Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + + This code was influenced by NodeMCU and Sming libraries, and first version of + Arduino wrapper written by Hristo Gochkov. + + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include "SDFS.h" +#include "SDFSFormatter.h" +#include + +using namespace fs; + + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SDFS) +FS SDFS = FS(FSImplPtr(new sdfs::SDFSImpl())); +#endif + +namespace sdfs { + +FileImplPtr SDFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode) +{ + if (!_mounted) { + DEBUGV("SDFSImpl::open() called on unmounted FS\n"); + return FileImplPtr(); + } + if (!path || !path[0]) { + DEBUGV("SDFSImpl::open() called with invalid filename\n"); + return FileImplPtr(); + } + int flags = _getFlags(openMode, accessMode); + if ((openMode && OM_CREATE) && strchr(path, '/')) { + // For file creation, silently make subdirs as needed. If any fail, + // it will be caught by the real file open later on + char *pathStr = strdup(path); + if (pathStr) { + // Make dirs up to the final fnamepart + char *ptr = strrchr(pathStr, '/'); + if (ptr && ptr != pathStr) { // Don't try to make root dir! + *ptr = 0; + _fs.mkdir(pathStr, true); + } + } + free(pathStr); + } + sdfat::File fd = _fs.open(path, flags); + if (!fd) { + DEBUGV("SDFSImpl::openFile: fd=%p path=`%s` openMode=%d accessMode=%d", + &fd, path, openMode, accessMode); + return FileImplPtr(); + } + auto sharedFd = std::make_shared(fd); + return std::make_shared(this, sharedFd, path); +} + +DirImplPtr SDFSImpl::openDir(const char* path) +{ + if (!_mounted) { + return DirImplPtr(); + } + char *pathStr = strdup(path); // Allow edits on our scratch copy + if (!pathStr) { + // OOM + return DirImplPtr(); + } + // Get rid of any trailing slashes + while (strlen(pathStr) && (pathStr[strlen(pathStr)-1]=='/')) { + pathStr[strlen(pathStr)-1] = 0; + } + // At this point we have a name of "/blah/blah/blah" or "blah" or "" + // If that references a directory, just open it and we're done. + sdfat::File dirFile; + const char *filter = ""; + if (!pathStr[0]) { + // openDir("") === openDir("/") + dirFile = _fs.open("/", sdfat::O_RDONLY); + filter = ""; + } else if (_fs.exists(pathStr)) { + dirFile = _fs.open(pathStr, sdfat::O_RDONLY); + if (dirFile.isDir()) { + // Easy peasy, path specifies an existing dir! + filter = ""; + } else { + dirFile.close(); + // This is a file, so open the containing dir + char *ptr = strrchr(pathStr, '/'); + if (!ptr) { + // No slashes, open the root dir + dirFile = _fs.open("/", sdfat::O_RDONLY); + filter = pathStr; + } else { + // We've got slashes, open the dir one up + *ptr = 0; // Remove slash, truncare string + dirFile = _fs.open(pathStr, sdfat::O_RDONLY); + filter = ptr + 1; + } + } + } else { + // Name doesn't exist, so use the parent dir of whatever was sent in + // This is a file, so open the containing dir + char *ptr = strrchr(pathStr, '/'); + if (!ptr) { + // No slashes, open the root dir + dirFile = _fs.open("/", sdfat::O_RDONLY); + filter = pathStr; + } else { + // We've got slashes, open the dir one up + *ptr = 0; // Remove slash, truncare string + dirFile = _fs.open(pathStr, sdfat::O_RDONLY); + filter = ptr + 1; + } + } + if (!dirFile) { + DEBUGV("SDFSImpl::openDir: path=`%s`\n", path); + return DirImplPtr(); + } + auto sharedDir = std::make_shared(dirFile); + auto ret = std::make_shared(filter, this, sharedDir, pathStr); + free(pathStr); + return ret; +} + +bool SDFSImpl::format() { + if (_mounted) { + return false; + } + SDFSFormatter formatter; + bool ret = formatter.format(&_fs, _cfg._csPin, _cfg._spiSettings); + return ret; +} + +time_t (*SDFSImpl::timeCallback)(void) = nullptr; + +}; // namespace sdfs + diff --git a/src/libs/SDFS/src/SDFS.h b/src/libs/SDFS/src/SDFS.h new file mode 100644 index 0000000..15d764d --- /dev/null +++ b/src/libs/SDFS/src/SDFS.h @@ -0,0 +1,537 @@ +#ifndef SDFS_H +#define SDFS_H + +/* + SDFS.h - file system wrapper for SdLib + Copyright (c) 2019 Earle F. Philhower, III. All rights reserved. + + Based on spiffs_api.h, which is: + | Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + + This code was influenced by NodeMCU and Sming libraries, and first version of + Arduino wrapper written by Hristo Gochkov. + + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include "FS.h" +#include "FSImpl.h" +#include "debug.h" +#include +#include +#include + +using namespace fs; + +namespace sdfs { + +class SDFSFileImpl; +class SDFSDirImpl; +class SDFSConfig : public FSConfig +{ +public: + static constexpr uint32_t FSId = 0x53444653; + + SDFSConfig(uint8_t csPin = 4, SPISettings spi = SD_SCK_MHZ(10)) : FSConfig(FSId, false), _csPin(csPin), _part(0), _spiSettings(spi) { } + + SDFSConfig setAutoFormat(bool val = true) { + _autoFormat = val; + return *this; + } + SDFSConfig setCSPin(uint8_t pin) { + _csPin = pin; + return *this; + } + SDFSConfig setSPI(SPISettings spi) { + _spiSettings = spi; + return *this; + } + SDFSConfig setPart(uint8_t part) { + _part = part; + return *this; + } + + // Inherit _type and _autoFormat + uint8_t _csPin; + uint8_t _part; + SPISettings _spiSettings; +}; + +class SDFSImpl : public FSImpl +{ +public: + SDFSImpl() : _mounted(false) + { + } + + FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override; + + bool exists(const char* path) override { + return _mounted ? _fs.exists(path) : false; + } + + DirImplPtr openDir(const char* path) override; + + bool rename(const char* pathFrom, const char* pathTo) override { + return _mounted ? _fs.rename(pathFrom, pathTo) : false; + } + + bool info64(FSInfo64& info) override { + if (!_mounted) { + DEBUGV("SDFS::info: FS not mounted\n"); + return false; + } + info.maxOpenFiles = 999; // TODO - not valid + info.blockSize = _fs.vol()->blocksPerCluster() * 512; + info.pageSize = 0; // TODO ? + info.maxPathLength = 255; // TODO ? + info.totalBytes =_fs.vol()->volumeBlockCount() * 512LL; + info.usedBytes = info.totalBytes - (_fs.vol()->freeClusterCount() * _fs.vol()->blocksPerCluster() * 512LL); + return true; + } + + bool info(FSInfo& info) override { + FSInfo64 i; + if (!info64(i)) { + return false; + } + info.blockSize = i.blockSize; + info.pageSize = i.pageSize; + info.maxOpenFiles = i.maxOpenFiles; + info.maxPathLength = i.maxPathLength; +#ifdef DEBUG_ESP_PORT + if (i.totalBytes > (uint64_t)SIZE_MAX) { + // This catches both total and used cases, since used must always be < total. + DEBUG_ESP_PORT.printf_P(PSTR("WARNING: SD card size overflow (%lld>= 4GB). Please update source to use info64().\n"), i.totalBytes); + } +#endif + info.totalBytes = (size_t)i.totalBytes; + info.usedBytes = (size_t)i.usedBytes; + return true; + } + + bool remove(const char* path) override { + return _mounted ? _fs.remove(path) : false; + } + + bool mkdir(const char* path) override { + return _mounted ? _fs.mkdir(path) : false; + } + + bool rmdir(const char* path) override { + return _mounted ?_fs.rmdir(path) : false; + } + + bool setConfig(const FSConfig &cfg) override + { + if ((cfg._type != SDFSConfig::FSId) || _mounted) { + DEBUGV("SDFS::setConfig: invalid config or already mounted\n"); + return false; + } + _cfg = *static_cast(&cfg); + return true; + } + + bool begin() override { + if (_mounted) { + end(); + } + _mounted = _fs.begin(_cfg._csPin, _cfg._spiSettings); + if (!_mounted && _cfg._autoFormat) { + format(); + _mounted = _fs.begin(_cfg._csPin, _cfg._spiSettings); + } + sdfat::SdFile::dateTimeCallback(dateTimeCB); + return _mounted; + } + + void end() override { + _mounted = false; + // TODO + } + + bool format() override; + + // The following are not common FS interfaces, but are needed only to + // support the older SD.h exports + uint8_t type() { + return _fs.card()->type(); + } + uint8_t fatType() { + return _fs.vol()->fatType(); + } + size_t blocksPerCluster() { + return _fs.vol()->blocksPerCluster(); + } + size_t totalClusters() { + return _fs.vol()->clusterCount(); + } + size_t totalBlocks() { + return (totalClusters() / blocksPerCluster()); + } + size_t clusterSize() { + return blocksPerCluster() * 512; // 512b block size + } + size_t size() { + return (clusterSize() * totalClusters()); + } + + // Helper function, takes FAT and makes standard time_t + static time_t FatToTimeT(uint16_t d, uint16_t t) { + struct tm tiempo; + memset(&tiempo, 0, sizeof(tiempo)); + tiempo.tm_sec = (((int)t) << 1) & 0x3e; + tiempo.tm_min = (((int)t) >> 5) & 0x3f; + tiempo.tm_hour = (((int)t) >> 11) & 0x1f; + tiempo.tm_mday = (int)(d & 0x1f); + tiempo.tm_mon = ((int)(d >> 5) & 0x0f) - 1; + tiempo.tm_year = ((int)(d >> 9) & 0x7f) + 80; + tiempo.tm_isdst = -1; + return mktime(&tiempo); + } + + // Because SdFat has a single, global setting for this we can only use a + // static member of our class to return the time/date. However, since + // this is static, we can't see the time callback variable. Punt for now, + // using time(NULL) as the best we can do. + static void dateTimeCB(uint16_t *dosYear, uint16_t *dosTime) { + + time_t now = (timeCallback == nullptr) ? time(nullptr) : (*timeCallback)(); + //time_t now = time(nullptr); + struct tm *tiempo = localtime(&now); + *dosYear = ((tiempo->tm_year - 80) << 9) | ((tiempo->tm_mon + 1) << 5) | tiempo->tm_mday; + *dosTime = (tiempo->tm_hour << 11) | (tiempo->tm_min << 5) | tiempo->tm_sec; + } + + virtual void setTimeCallback(time_t (*cb)(void)) + { + timeCallback = cb; + } + +protected: + friend class SDFileImpl; + friend class SDFSDirImpl; + + sdfat::SdFat* getFs() + { + return &_fs; + } + + static uint8_t _getFlags(OpenMode openMode, AccessMode accessMode) { + uint8_t mode = 0; + if (openMode & OM_CREATE) { + mode |= sdfat::O_CREAT; + } + if (openMode & OM_APPEND) { + mode |= sdfat::O_AT_END; + } + if (openMode & OM_TRUNCATE) { + mode |= sdfat::O_TRUNC; + } + if (accessMode & AM_READ) { + mode |= sdfat::O_READ; + } + if (accessMode & AM_WRITE) { + mode |= sdfat::O_WRITE; + } + return mode; + } + + sdfat::SdFat _fs; + SDFSConfig _cfg; + bool _mounted; + +private: + static time_t (*timeCallback)(void); +}; + + +class SDFSFileImpl : public FileImpl +{ +public: + SDFSFileImpl(SDFSImpl *fs, std::shared_ptr fd, const char *name) + : _fs(fs), _fd(fd), _opened(true) + { + _name = std::shared_ptr(new char[strlen(name) + 1], std::default_delete()); + strcpy(_name.get(), name); + } + + ~SDFSFileImpl() override + { + flush(); + close(); + } + + size_t write(const uint8_t *buf, size_t size) override + { + return _opened ? _fd->write(buf, size) : -1; + } + + size_t read(uint8_t* buf, size_t size) override + { + return _opened ? _fd->read(buf, size) : -1; + } + + void flush() override + { + if (_opened) { + _fd->flush(); + _fd->sync(); + } + } + + bool seek(uint32_t pos, SeekMode mode) override + { + if (!_opened) { + return false; + } + switch (mode) { + case SeekSet: + return _fd->seekSet(pos); + case SeekEnd: + return _fd->seekEnd(-pos); // TODO again, odd from POSIX + case SeekCur: + return _fd->seekCur(pos); + default: + // Should not be hit, we've got an invalid seek mode + DEBUGV("SDFSFileImpl::seek: invalid seek mode %d\n", mode); + assert((mode==SeekSet) || (mode==SeekEnd) || (mode==SeekCur)); // Will fail and give meaningful assert message + return false; + } + } + + size_t position() const override + { + return _opened ? _fd->curPosition() : 0; + } + + size_t size() const override + { + return _opened ? _fd->fileSize() : 0; + } + + bool truncate(uint32_t size) override + { + if (!_opened) { + DEBUGV("SDFSFileImpl::truncate: file not opened\n"); + return false; + } + return _fd->truncate(size); + } + + void close() override + { + if (_opened) { + _fd->close(); + _opened = false; + } + } + + const char* name() const override + { + if (!_opened) { + DEBUGV("SDFSFileImpl::name: file not opened\n"); + return nullptr; + } else { + const char *p = _name.get(); + const char *slash = strrchr(p, '/'); + // For names w/o any path elements, return directly + // If there are slashes, return name after the last slash + // (note that strrchr will return the address of the slash, + // so need to increment to ckip it) + return (slash && slash[1]) ? slash + 1 : p; + } + } + + const char* fullName() const override + { + return _opened ? _name.get() : nullptr; + } + + bool isFile() const override + { + return _opened ? _fd->isFile() : false;; + } + + bool isDirectory() const override + { + return _opened ? _fd->isDirectory() : false; + } + + time_t getLastWrite() override { + time_t ftime = 0; + if (_opened && _fd) { + sdfat::dir_t tmp; + if (_fd.get()->dirEntry(&tmp)) { + ftime = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime); + } + } + return ftime; + } + + time_t getCreationTime() override { + time_t ftime = 0; + if (_opened && _fd) { + sdfat::dir_t tmp; + if (_fd.get()->dirEntry(&tmp)) { + ftime = SDFSImpl::FatToTimeT(tmp.creationDate, tmp.creationTime); + } + } + return ftime; + } + + + +protected: + SDFSImpl* _fs; + std::shared_ptr _fd; + std::shared_ptr _name; + bool _opened; +}; + +class SDFSDirImpl : public DirImpl +{ +public: + SDFSDirImpl(const String& pattern, SDFSImpl* fs, std::shared_ptr dir, const char *dirPath = nullptr) + : _pattern(pattern), _fs(fs), _dir(dir), _valid(false), _dirPath(nullptr) + { + if (dirPath) { + _dirPath = std::shared_ptr(new char[strlen(dirPath) + 1], std::default_delete()); + strcpy(_dirPath.get(), dirPath); + } + } + + ~SDFSDirImpl() override + { + _dir->close(); + } + + FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) override + { + if (!_valid) { + return FileImplPtr(); + } + // MAX_PATH on FAT32 is potentially 260 bytes per most implementations + char tmpName[260]; + snprintf(tmpName, sizeof(tmpName), "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _lfn); + return _fs->open((const char *)tmpName, openMode, accessMode); + } + + const char* fileName() override + { + if (!_valid) { + DEBUGV("SDFSDirImpl::fileName: directory not valid\n"); + return nullptr; + } + return (const char*) _lfn; //_dirent.name; + } + + size_t fileSize() override + { + if (!_valid) { + return 0; + } + + return _size; + } + + time_t fileTime() override + { + if (!_valid) { + return 0; + } + + return _time; + } + + time_t fileCreationTime() override + { + if (!_valid) { + return 0; + } + + return _creation; + } + + bool isFile() const override + { + return _valid ? _isFile : false; + } + + bool isDirectory() const override + { + return _valid ? _isDirectory : false; + } + + bool next() override + { + const int n = _pattern.length(); + do { + sdfat::File file; + file.openNext(_dir.get(), sdfat::O_READ); + if (file) { + _valid = 1; + _size = file.fileSize(); + _isFile = file.isFile(); + _isDirectory = file.isDirectory(); + sdfat::dir_t tmp; + if (file.dirEntry(&tmp)) { + _time = SDFSImpl::FatToTimeT(tmp.lastWriteDate, tmp.lastWriteTime); + _creation = SDFSImpl::FatToTimeT(tmp.creationDate, tmp.creationTime); + } else { + _time = 0; + _creation = 0; + } + file.getName(_lfn, sizeof(_lfn)); + file.close(); + } else { + _valid = 0; + } + } while(_valid && strncmp((const char*) _lfn, _pattern.c_str(), n) != 0); + return _valid; + } + + bool rewind() override + { + _valid = false; + _dir->rewind(); + return true; + } + +protected: + String _pattern; + SDFSImpl* _fs; + std::shared_ptr _dir; + bool _valid; + char _lfn[64]; + time_t _time; + time_t _creation; + std::shared_ptr _dirPath; + uint32_t _size; + bool _isFile; + bool _isDirectory; +}; + +}; // namespace sdfs + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SDFS) +extern FS SDFS; +using sdfs::SDFSConfig; +#endif + +#endif // SDFS.h diff --git a/src/libs/SDFS/src/SDFSFormatter.h b/src/libs/SDFS/src/SDFSFormatter.h new file mode 100644 index 0000000..d1fe6c9 --- /dev/null +++ b/src/libs/SDFS/src/SDFSFormatter.h @@ -0,0 +1,405 @@ +/* + SDFSFormatter.cpp - Formatter for SdFat SD cards + Copyright (c) 2019 Earle F. Philhower, III. All rights reserved. + + A C++ implementation of the SdFat/examples/SdFormatter sketch: + | Copyright (c) 2011-2018 Bill Greiman + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SDFSFORMATTER_H +#define _SDFSFORMATTER_H + +#include "SDFS.h" +#include +#include + +namespace sdfs { + +class SDFSFormatter { +private: + // Taken from main FS object + sdfat::Sd2Card *card; + sdfat::cache_t *cache; + + uint32_t cardSizeBlocks; + uint32_t cardCapacityMB; + + + // MBR information + uint8_t partType; + uint32_t relSector; + uint32_t partSize; + + // Fake disk geometry + uint8_t numberOfHeads; + uint8_t sectorsPerTrack; + + // FAT parameters + uint16_t reservedSectors; + uint8_t sectorsPerCluster; + uint32_t fatStart; + uint32_t fatSize; + uint32_t dataStart; + + uint8_t writeCache(uint32_t lbn) { + return card->writeBlock(lbn, cache->data); + } + + void clearCache(uint8_t addSig) { + memset(cache, 0, sizeof(*cache)); + if (addSig) { + cache->mbr.mbrSig0 = sdfat::BOOTSIG0; + cache->mbr.mbrSig1 = sdfat::BOOTSIG1; + } + } + + bool clearFatDir(uint32_t bgn, uint32_t count) { + clearCache(false); + if (!card->writeStart(bgn, count)) { + DEBUGV("SDFS: Clear FAT/DIR writeStart failed"); + return false; + } + esp8266::polledTimeout::periodicFastMs timeToYield(5); // Yield every 5ms of runtime + for (uint32_t i = 0; i < count; i++) { + if (timeToYield) { + delay(0); // WDT feed + } + if (!card->writeData(cache->data)) { + DEBUGV("SDFS: Clear FAT/DIR writeData failed"); + return false; + } + } + if (!card->writeStop()) { + DEBUGV("SDFS: Clear FAT/DIR writeStop failed"); + return false; + } + return true; + } + + uint16_t lbnToCylinder(uint32_t lbn) { + return lbn / (numberOfHeads * sectorsPerTrack); + } + + uint8_t lbnToHead(uint32_t lbn) { + return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack; + } + + uint8_t lbnToSector(uint32_t lbn) { + return (lbn % sectorsPerTrack) + 1; + } + + bool writeMbr() { + clearCache(true); + sdfat::part_t* p = cache->mbr.part; + p->boot = 0; + uint16_t c = lbnToCylinder(relSector); + if (c > 1023) { + DEBUGV("SDFS: MBR CHS"); + return false; + } + p->beginCylinderHigh = c >> 8; + p->beginCylinderLow = c & 0XFF; + p->beginHead = lbnToHead(relSector); + p->beginSector = lbnToSector(relSector); + p->type = partType; + uint32_t endLbn = relSector + partSize - 1; + c = lbnToCylinder(endLbn); + if (c <= 1023) { + p->endCylinderHigh = c >> 8; + p->endCylinderLow = c & 0XFF; + p->endHead = lbnToHead(endLbn); + p->endSector = lbnToSector(endLbn); + } else { + // Too big flag, c = 1023, h = 254, s = 63 + p->endCylinderHigh = 3; + p->endCylinderLow = 255; + p->endHead = 254; + p->endSector = 63; + } + p->firstSector = relSector; + p->totalSectors = partSize; + if (!writeCache(0)) { + DEBUGV("SDFS: write MBR"); + return false; + } + return true; + } + + uint32_t volSerialNumber() { + return (cardSizeBlocks << 8) + micros(); + } + + bool makeFat16() { + uint16_t const BU16 = 128; + uint32_t nc; + for (dataStart = 2 * BU16;; dataStart += BU16) { + nc = (cardSizeBlocks - dataStart)/sectorsPerCluster; + fatSize = (nc + 2 + 255)/256; + uint32_t r = BU16 + 1 + 2 * fatSize + 32; + if (dataStart < r) { + continue; + } + relSector = dataStart - r + BU16; + break; + } + // check valid cluster count for FAT16 volume + if (nc < 4085 || nc >= 65525) { + DEBUGV("SDFS: Bad cluster count"); + } + reservedSectors = 1; + fatStart = relSector + reservedSectors; + partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32; + if (partSize < 32680) { + partType = 0X01; + } else if (partSize < 65536) { + partType = 0X04; + } else { + partType = 0X06; + } + // write MBR + if (!writeMbr()) { + DEBUGV("SDFS: writembr failed"); + return false; + } + + clearCache(true); + sdfat::fat_boot_t* pb = &cache->fbs; + pb->jump[0] = 0XEB; + pb->jump[1] = 0X00; + pb->jump[2] = 0X90; + for (uint8_t i = 0; i < sizeof(pb->oemId); i++) { + pb->oemId[i] = ' '; + } + pb->bytesPerSector = 512; + pb->sectorsPerCluster = sectorsPerCluster; + pb->reservedSectorCount = reservedSectors; + pb->fatCount = 2; + pb->rootDirEntryCount = 512; + pb->mediaType = 0XF8; + pb->sectorsPerFat16 = fatSize; + pb->sectorsPerTrack = sectorsPerTrack; + pb->headCount = numberOfHeads; + pb->hidddenSectors = relSector; + pb->totalSectors32 = partSize; + pb->driveNumber = 0X80; + pb->bootSignature = sdfat::EXTENDED_BOOT_SIG; + pb->volumeSerialNumber = volSerialNumber(); + memcpy_P(pb->volumeLabel, PSTR("NO NAME "), sizeof(pb->volumeLabel)); + memcpy_P(pb->fileSystemType, PSTR("FAT16 "), sizeof(pb->fileSystemType)); + // write partition boot sector + if (!writeCache(relSector)) { + DEBUGV("SDFS: FAT16 write PBS failed"); + return false; + } + // clear FAT and root directory + if (!clearFatDir(fatStart, dataStart - fatStart)) { + DEBUGV("SDFS: FAT16 clear root failed\n"); + return false; + } + clearCache(false); + cache->fat16[0] = 0XFFF8; + cache->fat16[1] = 0XFFFF; + // write first block of FAT and backup for reserved clusters + if (!writeCache(fatStart) || !writeCache(fatStart + fatSize)) { + DEBUGV("FAT16 reserve failed"); + return false; + } + return true; + } + + bool makeFat32() { + uint16_t const BU32 = 8192; + uint32_t nc; + relSector = BU32; + for (dataStart = 2 * BU32;; dataStart += BU32) { + nc = (cardSizeBlocks - dataStart)/sectorsPerCluster; + fatSize = (nc + 2 + 127)/128; + uint32_t r = relSector + 9 + 2 * fatSize; + if (dataStart >= r) { + break; + } + } + // error if too few clusters in FAT32 volume + if (nc < 65525) { + DEBUGV("SDFS: Bad cluster count"); + return false; + } + reservedSectors = dataStart - relSector - 2 * fatSize; + fatStart = relSector + reservedSectors; + partSize = nc * sectorsPerCluster + dataStart - relSector; + // type depends on address of end sector + // max CHS has lbn = 16450560 = 1024*255*63 + if ((relSector + partSize) <= 16450560) { + // FAT32 + partType = 0X0B; + } else { + // FAT32 with INT 13 + partType = 0X0C; + } + if (!writeMbr()) { + DEBUGV("SDFS: writembr failed"); + return false; + } + + clearCache(true); + + sdfat::fat32_boot_t* pb = &cache->fbs32; + pb->jump[0] = 0XEB; + pb->jump[1] = 0X00; + pb->jump[2] = 0X90; + for (uint8_t i = 0; i < sizeof(pb->oemId); i++) { + pb->oemId[i] = ' '; + } + pb->bytesPerSector = 512; + pb->sectorsPerCluster = sectorsPerCluster; + pb->reservedSectorCount = reservedSectors; + pb->fatCount = 2; + pb->mediaType = 0XF8; + pb->sectorsPerTrack = sectorsPerTrack; + pb->headCount = numberOfHeads; + pb->hidddenSectors = relSector; + pb->totalSectors32 = partSize; + pb->sectorsPerFat32 = fatSize; + pb->fat32RootCluster = 2; + pb->fat32FSInfo = 1; + pb->fat32BackBootBlock = 6; + pb->driveNumber = 0X80; + pb->bootSignature = sdfat::EXTENDED_BOOT_SIG; + pb->volumeSerialNumber = volSerialNumber(); + memcpy_P(pb->volumeLabel, PSTR("NO NAME "), sizeof(pb->volumeLabel)); + memcpy_P(pb->fileSystemType, PSTR("FAT32 "), sizeof(pb->fileSystemType)); + // write partition boot sector and backup + if (!writeCache(relSector) || !writeCache(relSector + 6)) { + DEBUGV("SDFS: FAT32 write PBS failed"); + return false; + } + clearCache(true); + // write extra boot area and backup + if (!writeCache(relSector + 2) || !writeCache(relSector + 8)) { + DEBUGV("SDFS: FAT32 PBS ext failed"); + return false; + } + sdfat::fat32_fsinfo_t* pf = &cache->fsinfo; + pf->leadSignature = sdfat::FSINFO_LEAD_SIG; + pf->structSignature = sdfat::FSINFO_STRUCT_SIG; + pf->freeCount = 0XFFFFFFFF; + pf->nextFree = 0XFFFFFFFF; + // write FSINFO sector and backup + if (!writeCache(relSector + 1) || !writeCache(relSector + 7)) { + DEBUGV("SDFS: FAT32 FSINFO failed"); + return false; + } + clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster); + clearCache(false); + cache->fat32[0] = 0x0FFFFFF8; + cache->fat32[1] = 0x0FFFFFFF; + cache->fat32[2] = 0x0FFFFFFF; + // write first block of FAT and backup for reserved clusters + if (!writeCache(fatStart) || !writeCache(fatStart + fatSize)) { + DEBUGV("SDFS: FAT32 reserve failed"); + return false; + } + return true; + } + +public: + bool format(sdfat::SdFat *_fs, int8_t _csPin, SPISettings _spiSettings) { + card = static_cast(_fs->card()); + cache = _fs->cacheClear(); + + if (!card->begin(_csPin, _spiSettings)) { + return false; + } + cardSizeBlocks = card->cardSize(); + if (cardSizeBlocks == 0) { + return false; + } + + cardCapacityMB = (cardSizeBlocks + 2047)/2048; + + if (cardCapacityMB <= 6) { + return false; // Card is too small + } else if (cardCapacityMB <= 16) { + sectorsPerCluster = 2; + } else if (cardCapacityMB <= 32) { + sectorsPerCluster = 4; + } else if (cardCapacityMB <= 64) { + sectorsPerCluster = 8; + } else if (cardCapacityMB <= 128) { + sectorsPerCluster = 16; + } else if (cardCapacityMB <= 1024) { + sectorsPerCluster = 32; + } else if (cardCapacityMB <= 32768) { + sectorsPerCluster = 64; + } else { + // SDXC cards + sectorsPerCluster = 128; + } + + // set fake disk geometry + sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63; + + if (cardCapacityMB <= 16) { + numberOfHeads = 2; + } else if (cardCapacityMB <= 32) { + numberOfHeads = 4; + } else if (cardCapacityMB <= 128) { + numberOfHeads = 8; + } else if (cardCapacityMB <= 504) { + numberOfHeads = 16; + } else if (cardCapacityMB <= 1008) { + numberOfHeads = 32; + } else if (cardCapacityMB <= 2016) { + numberOfHeads = 64; + } else if (cardCapacityMB <= 4032) { + numberOfHeads = 128; + } else { + numberOfHeads = 255; + } + + // Erase all data on card (TRIM) + uint32_t const ERASE_SIZE = 262144L; + uint32_t firstBlock = 0; + uint32_t lastBlock; + do { + lastBlock = firstBlock + ERASE_SIZE - 1; + if (lastBlock >= cardSizeBlocks) { + lastBlock = cardSizeBlocks - 1; + } + if (!card->erase(firstBlock, lastBlock)) { + return false; // Erase fail + } + delay(0); // yield to the OS to avoid WDT + firstBlock += ERASE_SIZE; + } while (firstBlock < cardSizeBlocks); + + if (!card->readBlock(0, cache->data)) { + return false; + } + + if (card->type() != sdfat::SD_CARD_TYPE_SDHC) { + return makeFat16(); + } else { + return makeFat32(); + } + } +}; // class SDFSFormatter + +}; // namespace sdfs + + +#endif // _SDFSFORMATTER_H