diff --git a/Debug.h b/Debug.h index 8d939dd..9453c0b 100644 --- a/Debug.h +++ b/Debug.h @@ -2,8 +2,12 @@ #define DEBUG_H #include + + #include "Platforms.h" + + /* extern "C" { #include "ets_sys.h" @@ -25,25 +29,64 @@ extern "C" { } */ +class Log { + +public: + + template static void addInfo(const char* module, const char* fmt, Args... args) { + add('i', module, fmt, args...); + } + + template static void addError(const char* module, const char* fmt, Args... args) { + add('e', module, fmt, args...); + while(true) {} + } + +private: + + template static void add(char level, const char* module, const char* fmt, Args... args) { + + char buf[128]; + char* dst = buf; + + dst = dst + sprintf(dst, "%c[%-10s] ", level, module); + dst = dst + sprintf(dst, fmt, args...); + dst = dst + sprintf(dst, "\n"); + + #ifdef IS_DESKTOP + printf(buf); + #elif TEENSY + Serial.print(buf); + #elif ESP8266 + os_printf(buf); + #elif ESP32 + printf(buf); + #else + #error "unsupported platform" + #endif + + } + +}; #if (!defined(DEBUG)) - #define debug(str) - #define debugMod(module, str) - #define debugMod1(module, str, val) - #define debugMod2(module, str, v1, v2) - #define debugMod3(module, str, v1, v2, v3) - #define debugMod4(module, str, v1, v2, v3, v4) - #define debugMod5(module, str, v1, v2, v3, v4, v5) - #define debugMod6(module, str, v1, v2, v3, v4, v5, v6) - #define debugMod7(module, str, v1, v2, v3, v4, v5, v6, v7) - #define debugMod8(module, str, v1, v2, v3, v4, v5, v6, v7, v8) - #define debugMod9(module, str, v1, v2, v3, v4, v5, v6, v7, v8, v9) - #define IF_DEBUG(a) - #define debugShow(a, b) +// #define debug(str) +// #define debugMod(module, str) +// #define debugMod1(module, str, val) +// #define debugMod2(module, str, v1, v2) +// #define debugMod3(module, str, v1, v2, v3) +// #define debugMod4(module, str, v1, v2, v3, v4) +// #define debugMod5(module, str, v1, v2, v3, v4, v5) +// #define debugMod6(module, str, v1, v2, v3, v4, v5, v6) +// #define debugMod7(module, str, v1, v2, v3, v4, v5, v6, v7) +// #define debugMod8(module, str, v1, v2, v3, v4, v5, v6, v7, v8) +// #define debugMod9(module, str, v1, v2, v3, v4, v5, v6, v7, v8, v9) +// #define IF_DEBUG(a) +// #define debugShow(a, b) - #warning "not using debug output" +// #warning "not using debug output" #elif ESP8266 diff --git a/Platforms.h b/Platforms.h index 60dd982..ccaf83e 100644 --- a/Platforms.h +++ b/Platforms.h @@ -11,9 +11,12 @@ #define TEENSY_41 41 +#define DESKTOP 99 + #define ESP8266 (PLATFORM == WEMOS_D1_MINI) || (PLATFORM == NODE_MCU) #define ESP32 (PLATFORM == WROOM32_DEVKIT) || (PLATFORM == TTGO) #define TEENSY (PLATFORM == TEENSY_41) +#define IS_DESKTOP (PLATFORM == DESKTOP) #ifndef PLATFORM #error "PLATFORM compile time variable not defined" @@ -36,6 +39,10 @@ #pragma message "Using Teensy" #define DELAY_MS(ms) delay(ms) +#elif (IS_DESKTOP) + + #pragma message "Using Desktop" + #define DELAY_MS(ms) delay(ms) #else diff --git a/ext/lcd/ILI9486p.h b/ext/lcd/ILI9486p.h index a6b487e..0e1679b 100644 --- a/ext/lcd/ILI9486p.h +++ b/ext/lcd/ILI9486p.h @@ -457,7 +457,7 @@ private: MyGPIO::clear(PIN_RESET); delay(250); MyGPIO::set(PIN_RESET); - delay(400); + delay(250); } } diff --git a/ext/sd/AccessHelper.h b/ext/sd/AccessHelper.h new file mode 100644 index 0000000..9fbdb86 --- /dev/null +++ b/ext/sd/AccessHelper.h @@ -0,0 +1,134 @@ +#pragma once + +#include "Types.h" +#include "../../Debug.h" + +/** + * wrapper class + * provides byte-based read/write access + * to block-based devices which only support + * reading/writing aligned blocks + */ +template class AccessHelper { + + static constexpr const uint32_t SEC_SIZE = 512; + static constexpr const char* NAME = "AccessHlp"; + + Dev& dev; + + +public: + + AccessHelper(Dev& dev) : dev(dev) { + ; + } + + /** write size bytes starting at addr using data from src, supports writing partial blocks by reading them first */ + uint32_t write(AbsPos addr, uint32_t size, uint8_t* src) { + + Log::addInfo(NAME, "write(%d @ %d)", size, addr); + + uint32_t written = 0; + LBA512 addrLBA = addr / SEC_SIZE; // LBA address + uint16_t offset = (addr - addrLBA*SEC_SIZE);// 0 when addr is SEC_SIZE-byte aligned + uint8_t buf[SEC_SIZE]; + + while(size) { + + if (offset || size < SEC_SIZE) { // non-aligned / non-full-block write + + // read the whole sector + if (!readSingleBlock(addrLBA, buf)) {return written;} + + // merge in the new data + const uint32_t toModify = min(SEC_SIZE-offset, size); + for (uint16_t i = 0; i < toModify; ++i) {buf[i+offset] = src[i+written];} + offset = 0; + + // write back the modified sector + if (!writeSingleBlock(addrLBA, buf)) {return written;} + + ++addrLBA; + size -= toModify; + written += toModify; + + } else { + + // write a full block + if (!writeSingleBlock(addrLBA, &src[written])) {return written;} + + ++addrLBA; + size -= SEC_SIZE; + written += SEC_SIZE; + + } + + } + + return written; + + } + + /** read size bytes starting at addr into dst */ + uint32_t read(AbsPos addr, uint32_t size, uint8_t* dst) { + + Log::addInfo(NAME, "read(%d @ %d)", size, addr); + + uint32_t read = 0; + LBA512 addrLBA = addr / SEC_SIZE; // LBA address + uint16_t offset = (addr - addrLBA*SEC_SIZE);// 0 when addr is SEC_SIZE-byte aligned, otherwise within [1:511] + + while(size) { + + if (offset || size < SEC_SIZE) { // non-aligned read / non-full-block read + + const uint32_t toRead = min(SEC_SIZE-offset, size); + if (!readSingleBlock(addrLBA, &dst[read], offset, toRead)) {return read;} + offset = 0; // all following reads are aligned + + ++addrLBA; + size -= toRead; + read += toRead; + + } else { // full block read + + if (!readSingleBlock(addrLBA, &dst[read])) {return read;} + ++addrLBA; + size -= SEC_SIZE; + read += SEC_SIZE; + + } + + } + + return read; + + } + + /** read a single block of SEC_SIZE bytes. addr = byteAddr/512 */ + bool readSingleBlock(LBA512 addr, uint8_t* dst) { + return dev.readSingleBlock(addr, dst); + } + + /** read a single block of SEC_SIZE bytes. addr = byteAddr/512, write only a fraction of the 512 bytes into dst (skip+len) */ + bool readSingleBlock(LBA512 addr, uint8_t* dst, uint16_t skip, uint16_t len) { + uint8_t buf[SEC_SIZE]; + if (!dev.readSingleBlock(addr, buf)) {return false;} + for (int i = 0; i < len; ++i) { + *dst = buf[i+skip]; + ++dst; + } + return true; + } + + /** write a single block of 512 bytes. addr = byteAddr/512 */ + bool writeSingleBlock(LBA512 addr, uint8_t* dst) { + return dev.writeSingleBlock(addr, dst); + } + + +private: + + static inline uint32_t min(uint32_t a, uint32_t b) {return (a < b) ? a : b;} + +}; diff --git a/ext/sd/CMakeLists.txt b/ext/sd/CMakeLists.txt new file mode 100755 index 0000000..ca4a428 --- /dev/null +++ b/ext/sd/CMakeLists.txt @@ -0,0 +1,73 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.0) + +# select build type +SET( CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" ) + +PROJECT(SD) + +IF(NOT CMAKE_BUILD_TYPE) + MESSAGE(STATUS "No build type selected. Default to Debug") + SET(CMAKE_BUILD_TYPE "Debug") +ENDIF() + + + +INCLUDE_DIRECTORIES( + ./ +) + +FILE(GLOB HEADERS + ./*.h + ./*/*.h +) + + +FILE(GLOB SOURCES + ./*.cpp + ./*/*.cpp +) + +ADD_DEFINITIONS( + + -std=gnu++17 + + -Wall + -Werror=return-type + -Wextra + -Wpedantic + -Warray-bounds + -fstack-protector-all + + + -g3 + -O0 + + -DWITH_TESTS + -DWITH_ASSERTIONS + -DWITH_DEBUG_LOG + -D_GLIBCXX_DEBUG + +) + + +# build a binary file +ADD_EXECUTABLE( + ${PROJECT_NAME} + ${HEADERS} + ${SOURCES} +) + +#SET(EXTRA_LIBS ${EXTRA_LIBS} nl-genl-3 nl-3) +#INCLUDE_DIRECTORIES(/usr/include/libnl3/) +#SET(EXTRA_LIBS ${EXTRA_LIBS} iw) + +# needed external libraries +TARGET_LINK_LIBRARIES( + ${PROJECT_NAME} + gtest +# pthread + ${EXTRA_LIBS} +) + +SET(CMAKE_C_COMPILER ${CMAKE_CXX_COMPILER}) + diff --git a/ext/sd/MBR.h b/ext/sd/MBR.h index a9639f4..0118d19 100644 --- a/ext/sd/MBR.h +++ b/ext/sd/MBR.h @@ -2,6 +2,8 @@ template class MBR { + static constexpr const char* NAME = "MBR"; + BlockDev& dev; bool present = false; bool valid = false; @@ -59,7 +61,13 @@ private: p->type = src[0x4]; p->firstSector = getU32(src, 8); p->numSectors = getU32(src, 12); - + + if (p->type) { + Log::addInfo(NAME, "Part[%i] At:%d*512, Len:%d*512, Type:%d", i, p->firstSector, p->numSectors, p->type); + } else { + Log::addInfo(NAME, "Part[%i] -", i); + } + } } diff --git a/ext/sd/SDCard.h b/ext/sd/SDCard.h index 2bbe756..d781855 100644 --- a/ext/sd/SDCard.h +++ b/ext/sd/SDCard.h @@ -9,11 +9,13 @@ #define TEENSY_SD_PIN_MISO 43 +#include "Types.h" + // http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf // https://www.convict.lu/pdf/ProdManualSDCardv1.9.pdf // http://rjhcoding.com/avrc-sd-interface-3.php template class SDCard { - + SPI& spi; union R1 { // SanDisk Manual Page 5-13 @@ -64,7 +66,7 @@ public: bool init() { MyGPIO::setOutput(PIN_CS); - debugMod(NAME, "init()"); + Log::addInfo(NAME, "init()"); // RESET: MOSI = 1, CS = 1 (deselected!!), at least 74 Clocks deselect(); @@ -74,7 +76,7 @@ public: for (uint8_t i = 0; ; ++i) { const R1 res = cmd0(); if (res.raw == 1) {break;} - if (i == 4) {debugMod(NAME, "init failed"); return false;} + if (i == 4) {Log::addError(NAME, "init failed"); return false;} delay(50); } @@ -82,13 +84,13 @@ public: // TODO? // Version 2.0 Card Rcmd8 r8 = cmd8(); - + if (r8.state.raw == 1) { - debugMod(NAME, "V2/V3 Card"); + Log::addInfo(NAME, "V2/V3 Card"); if (r8.data[2] == 0x01 && r8.data[3] == 0xAA) { - debugMod(NAME, "Pattern Correct"); + Log::addInfo(NAME, "Pattern Correct"); } else { - debugMod(NAME, "Pattern Mismatch"); + Log::addError(NAME, "Pattern Mismatch"); return false; } } @@ -103,70 +105,35 @@ public: for (uint8_t i = 0; ; ++i) { delay(100); R1 r1 = acmd41(hcs); - if (i == 8) {debugMod(NAME, "init failed"); return false;} - if (r1.raw == 0) {break;} // finished - if (r1.raw == 1) {continue;} // card is still idle - if (r1.raw > 1) {debugMod(NAME, "init failed"); return false;} + if (i == 8) {Log::addError(NAME, "init failed"); return false;} + if (r1.raw == 0) {break;} // finished + if (r1.raw == 1) {continue;} // card is still idle + if (r1.raw > 1) {Log::addError(NAME, "init failed"); return false;} } - debugMod(NAME, "init OK"); + Log::addInfo(NAME, "init OK"); return true; } - uint32_t read(uint32_t addr, uint32_t size, uint8_t* dst) { + + /** read a single block of 512 bytes, addr = byteAddr/512 */ + bool readSingleBlock(LBA512 addr, uint8_t* dst) { - uint32_t read = 0; - uint32_t readAddr = addr & (0xFFFFFFFF - 512); // 512 byte aligned staring address - - if (readAddr != addr) { // non-aligned first read? - uint8_t buf[512]; - uint16_t cOff = (addr-readAddr); // skip the unneeded bytes - uint16_t cSize = 512 - cOff; - readSingleBlock(readAddr, buf); // read a full block - memcpy(dst, buf+cOff, cSize); // copy only the required bytes - size -= cSize; - dst += cSize; - read += cSize; - readAddr += 512; - } - - while(size >= 512) { - readSingleBlock(readAddr, dst); - readAddr += 512; - dst += 512; - read += 512; - size -= 512; - } - - if (size > 0) { - uint8_t buf[512]; - readSingleBlock(readAddr, buf); - memcpy(dst, buf, size); - read += size; - } - - return read; - - } - - /** read a single block of 512 bytes */ - bool readSingleBlock(uint32_t addr, uint8_t* dst) { - - addr = addr / 512; sendCMD(17, addr>>24, addr>>16, addr>>8, addr>>0, 0xFF); R1 res; readResponse(&res.raw, 1); - debugMod2(NAME, "readSingleBlock(%08x): %02x", addr, res.raw); - + + Log::addInfo(NAME, "readBlock(%d*512): %02x", addr, res.raw); + // read command OK? - if (res.raw != 0) {endCMD(); return false;} + if (res.raw != 0) {Log::addError(NAME, "failed"); endCMD(); return false;} // wait for data to become available for (uint16_t i = 0; ; ++i) { uint8_t res = spi.readWriteByte(0xFF); if (res == 0xFE) {break;} // available! - if (res != 0xFF) {debugMod(NAME, "invalid"); endCMD(); return false;} // invalid response - if (i > 1024) {debugMod(NAME, "timeout"); endCMD(); return false;} // timeout + if (res != 0xFF) {Log::addError(NAME, "invalid"); endCMD(); return false;} // invalid response + if (i > 1024) {Log::addError(NAME, "timeout"); endCMD(); return false;} // timeout } // read data @@ -181,6 +148,7 @@ public: } private: + /** send a new command */ @@ -207,7 +175,7 @@ private: sendCMD(0, 0x00, 0x00, 0x00, 0x00, 0x4A); R1 res; readResponse(&res.raw, 1); endCMD(); - debugMod1(NAME, "cmd0: %02x", res.raw); + Log::addInfo(NAME, "cmd0: %02x", res.raw); return res; } @@ -221,7 +189,7 @@ private: //} Rcmd8 res; readResponse(res.raw, 5); endCMD(); - debugMod5(NAME, "cmd8: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]); + Log::addInfo(NAME, "cmd8: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]); return res; } @@ -233,7 +201,7 @@ private: sendCMD(58, 0x00, 0x00, 0x00, 0x00, 0xFF);//0b0111010); R3 res; readResponse(res.raw, 5); // typical response: 0x01 0x00 0xff 0x80 0x00 endCMD(); - debugMod5(NAME, "cmd58: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]); + Log::addInfo(NAME, "cmd58: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]); return res; } @@ -244,7 +212,7 @@ private: sendCMD(55, 0x00, 0x00, 0x00, 0x00, 0xFF); R1 res; readResponse(&res.raw, 1); endCMD(); - debugMod1(NAME, "cmd55: %02x", res.raw); + Log::addInfo(NAME, "cmd55: %02x", res.raw); return res; } @@ -253,7 +221,7 @@ private: sendCMD(41, val>>24, val>>16, val>>8, val>>0, 0xFF); R1 res; readResponse(&res.raw, 1); endCMD(); - debugMod1(NAME, "acmd41: %02x", res.raw); + Log::addInfo(NAME, "acmd41: %02x", res.raw); return res; } @@ -271,7 +239,7 @@ private: // NOTE: it is IMPORTANT to send 0xFF to the card while reading its responses! // wait for the first byte to arrive and read it - for (uint8_t i = 0; i < 4; ++i) { + for (uint8_t i = 0; i < 8; ++i) { dst[0] = spi.readWriteByte(0xFF); if ( (dst[0] & 0x80) == 0 ) {break;} } diff --git a/ext/sd/Types.h b/ext/sd/Types.h new file mode 100644 index 0000000..c782e08 --- /dev/null +++ b/ext/sd/Types.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +using LBA512 = uint32_t; + +using AbsPos = uint32_t; +using AbsOffset = uint32_t; diff --git a/ext/sd/fat32/DirIterator.h b/ext/sd/fat32/DirIterator.h index d8459f3..ccd14e1 100644 --- a/ext/sd/fat32/DirIterator.h +++ b/ext/sd/fat32/DirIterator.h @@ -1,45 +1,79 @@ class DirIterator { + static constexpr const char* NAME = "FAT32_DirI"; + FS& fs; - ClusterNr nextCluster; - int curEntryInCluster; + ClusterNr curCluster; + uint8_t curEntryInSector; // current DirEntry within the current sector + uint8_t curSectorInCluster; // current Sector within the current cluster + uint8_t buf[512]; public: - DirIterator(FS& fs, ClusterNr clusterNr) : fs(fs), nextCluster(clusterNr), curEntryInCluster(255) { + DirIterator(FS& fs, ClusterNr clusterNr) : fs(fs), curCluster(clusterNr), curEntryInSector(0), curSectorInCluster(0) { + + Log::addInfo(NAME, "init @ Cluster %d", curCluster); + + // read the first sector in the first cluster + read(curCluster, 0); } - bool hasNext() { + + DirEntry* next() { while(true) { - ++curEntryInCluster; + // end of sector reached? + if (curEntryInSector >= fs.tmp.dirEntriesPerSector) { + + // next sector + ++curSectorInCluster; + curEntryInSector = 0; + + // end of cluster reached? + if (curSectorInCluster >= fs.desc.sectorsPerCluster) { + + // find next cluster + curCluster = fs.getNextCluster(curCluster); // number of the next cluster (if any) + curSectorInCluster = 0; + + } + + // fetch from disk + read(curCluster, curSectorInCluster); - // reached end of cluster? load the next one - if (curEntryInCluster > fs.tmp.dirEntriesPerSector) { - fs.dev.read(fs.clusterToAbsPos(nextCluster), 512, buf); - nextCluster = fs.getNextCluster(nextCluster); - curEntryInCluster = 0; } - DirEntry* desc = reinterpret_cast(buf + (sizeof(DirEntry) * curEntryInCluster)); + // the current entry + DirEntry* dirEntry = reinterpret_cast(buf + (sizeof(DirEntry) * curEntryInSector)); + ++curEntryInSector; - if (desc->isLongFileName()) {continue;} - if (desc->isUnused()) {continue;} - if (desc->isEndOfDirectory()) {return false;} + // check it + if (dirEntry->isLongFileName()) {continue;} + if (dirEntry->isUnused()) {continue;} + if (dirEntry->isEndOfDirectory()) { + return nullptr; + } - return true; + // usable! + return dirEntry; } } - DirEntry next() { - DirEntry* de = reinterpret_cast(buf + (sizeof(DirEntry) * curEntryInCluster)); - return *de; +private: + + /** fetch one sector within a cluster */ + void read(ClusterNr clusterNr, uint8_t sectorInCluster) { + Log::addInfo(NAME, "fetching sector %d in clusterNr %d", sectorInCluster, clusterNr) ; + const AbsPos pos = fs.clusterToAbsPos(clusterNr) + (curSectorInCluster * fs.desc.bytesPerSector); + fs.dev.read(pos, fs.desc.bytesPerSector, buf); } + + }; diff --git a/ext/sd/fat32/FS.h b/ext/sd/fat32/FS.h index 275aa5e..7c74dae 100644 --- a/ext/sd/fat32/FS.h +++ b/ext/sd/fat32/FS.h @@ -1,6 +1,8 @@ #pragma once #include +#include + #include "Structs.h" // https://www.pjrc.com/tech/8051/ide/fat32.html @@ -23,6 +25,9 @@ namespace FAT32 { #include "File.h" #include "DirIterator.h" + #include "WriteFile.h" + #include "FreeClusterIterator.h" + /** ctor with the absolute offset addr (in bytes) */ FS(BlockDev& dev, AbsOffset offset) : dev(dev), offset(offset) { @@ -41,14 +46,25 @@ namespace FAT32 { /** open the given file for reading*/ File open(const DirEntry& de) { - return File(*this, de.getFirstCluster(), de.size); + return File(*this, de.size, de.getFirstCluster()); } +// /** create a new file for writing, in the given directory */ +// WriteFile newFile(DirEntry& de, const uint32_t size) { +// if (!de.isDirectory()) {return nullptr;} +// uint32_t allocSize = 0; +// FreeClusterIterator fci(*this); +// while(allocSize < size) { +// ClusterNr = fci.next(); +// } +// } + private: void init() { - debugMod3(NAME") + Log::addInfo(NAME, "init @ %d", offset); + uint8_t buf[512]; dev.read(offset, 512, buf); @@ -68,6 +84,8 @@ namespace FAT32 { tmp.startOfFirstRootDirCluster = clusterToAbsPos(desc.rootDirFirstCluster); tmp.dirEntriesPerSector = desc.bytesPerSector / sizeof(DirEntry); + Log::addInfo(NAME, "Bytes/Sector: %d, Sector/Cluster: %d, FATs: %d, RootDir: %d", desc.bytesPerSector, desc.sectorsPerCluster, desc.numberOfFATs, desc.rootDirFirstCluster); + /* std::cout << (int)desc.bytesPerSector << std::endl; std::cout << (int)desc.sectorsPerCluster << std::endl; @@ -86,8 +104,9 @@ namespace FAT32 { /** determine the ClusterNr following the given ClusterNr */ ClusterNr getNextCluster(ClusterNr clusterNr) { const AbsPos pos = tmp.startOfFAT + ((clusterNr) * sizeof(uint32_t)); - ClusterNr next; - int read = dev.read(pos, 4, reinterpret_cast(&next)); + ClusterNr next = 0; + dev.read(pos, 4, reinterpret_cast(&next)); + Log::addInfo(NAME, "nextCluster(%d) -> %d", clusterNr, next); return next; } diff --git a/ext/sd/fat32/FS.h.autosave b/ext/sd/fat32/FS.h.autosave deleted file mode 100644 index 543f305..0000000 --- a/ext/sd/fat32/FS.h.autosave +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include -#include "Structs.h" - -// https://www.pjrc.com/tech/8051/ide/fat32.html -namespace FAT32 { - - template class FS { - - static constexpr const char* NAME = "FAT32"; - - BlockDev& dev; - AbsOffset offset; - - - FSDesc desc; - Precomputed tmp; - bool valid = false; - - - public: - - #include "File.h" - #include "DirIterator.h" - - /** ctor with the absolute offset addr (in bytes) */ - FS(BlockDev& dev, AbsOffset offset) : dev(dev), offset(offset) { - init(); - } - - /** is the detected FS valid? */ - bool isValid() const { - return valid; - } - - /** get an iterator for the root directory */ - DirIterator getRoot() { - return DirIterator(*this, desc.rootDirFirstCluster); - } - - /** open the given file for reading*/ - File open(const DirEntry& de) { - return File(*this, de.getFirstCluster(), de.size); - } - - private: - - void init() { - - debugMod3(NAME) - uint8_t buf[512]; - dev.read(offset, 512, buf); - - desc.bytesPerSector = getU16(&buf[0x0B]); - desc.sectorsPerCluster = getU8(&buf[0x0D]); - desc.numReservedSectors = getU16(&buf[0x0E]); - desc.numberOfFATs = getU8(&buf[0x10]); - desc.sectorsPerFAT = getU32(&buf[0x24]); - desc.rootDirFirstCluster = getU32(&buf[0x2C]); - - // basic sanity check based on constants - valid = (desc.bytesPerSector == 512) && (desc.numberOfFATs == 2) && (getU16(&buf[0x1FE]) == 0xAA55); - - tmp.bytesPerCluster = desc.sectorsPerCluster * desc.bytesPerSector; - tmp.startOfFAT = offset + (desc.numReservedSectors * desc.bytesPerSector); - tmp.startOfFirstDataCluster = offset + (desc.numReservedSectors * desc.bytesPerSector) + (desc.numberOfFATs * desc.sectorsPerFAT * desc.bytesPerSector); - tmp.startOfFirstRootDirCluster = clusterToAbsPos(desc.rootDirFirstCluster); - tmp.dirEntriesPerSector = desc.bytesPerSector / sizeof(DirEntry); - - /* - std::cout << (int)desc.bytesPerSector << std::endl; - std::cout << (int)desc.sectorsPerCluster << std::endl; - std::cout << (int)desc.numReservedSectors << std::endl; - std::cout << (int)desc.numberOfFATs << std::endl; - std::cout << (int)desc.sectorsPerFAT << std::endl; - std::cout << (int)desc.rootDirFirstCluster << std::endl; - - std::cout << tmp.startOfFAT << std::endl; - std::cout << tmp.startOfFirstDataCluster << std::endl; - std::cout << tmp.startOfFirstRootDirCluster << std::endl; - */ - - } - - /** determine the ClusterNr following the given ClusterNr */ - ClusterNr getNextCluster(ClusterNr clusterNr) { - const AbsPos pos = tmp.startOfFAT + ((clusterNr) * sizeof(uint32_t)); - ClusterNr next; - int read = dev.read(pos, 4, reinterpret_cast(&next)); - return next; - } - - /** convert ClusterNr into an absolute position on disk */ - AbsPos clusterToAbsPos(ClusterNr clusterNr) { - return tmp.startOfFirstDataCluster + ((clusterNr - 2) * desc.sectorsPerCluster * desc.bytesPerSector); - } - - }; -} diff --git a/ext/sd/fat32/File.h b/ext/sd/fat32/File.h index f7be642..a9f24f3 100644 --- a/ext/sd/fat32/File.h +++ b/ext/sd/fat32/File.h @@ -1,6 +1,7 @@ class File { - static constexpr const int32_t F_EOF = -1; + static constexpr const char* NAME = "FAT32_File"; + static constexpr const uint32_t F_EOF = 0xFFFFFFFF; FS& fs; uint32_t totalSize; @@ -12,23 +13,33 @@ class File { public: - File(FS& fs, uint32_t firstCluster, uint32_t size) : fs(fs), totalSize(size), curCluster(firstCluster) { - + File(FS& fs, uint32_t size, uint32_t firstCluster) : fs(fs), totalSize(size), curCluster(firstCluster) { + Log::addInfo(NAME, "init @ cluster %d", firstCluster); } /** the file's size */ uint32_t getSize() const {return totalSize;} - uint32_t read(uint32_t size, uint8_t* dst) { - uint32_t total = 0; - while(true) { - const uint32_t read = _read(size, dst); - if (read == F_EOF) {break;} - size -= read; + uint32_t read(uint32_t size, uint8_t* dst, std::function callback) { + + Log::addInfo(NAME, "read %d bytes", size); + + uint32_t remaining = size; + uint32_t totalRead = 0; + + while(remaining) { + const uint32_t read = _read(remaining, dst); + if (read == F_EOF) { + Log::addInfo(NAME, "EOF"); break; + } + remaining -= read; dst += read; - total += read; + totalRead += read; + callback(totalRead*100/size); } - return total; + + return totalRead; + } private: @@ -44,6 +55,7 @@ private: if (posInCluster == fs.tmp.bytesPerCluster) { curCluster = fs.getNextCluster(curCluster); posInCluster = 0; + Log::addInfo(NAME, "next cluster %d", curCluster); } // how many bytes are left in the current cluster? diff --git a/ext/sd/fat32/FreeClusterIterator.h b/ext/sd/fat32/FreeClusterIterator.h new file mode 100644 index 0000000..49b575c --- /dev/null +++ b/ext/sd/fat32/FreeClusterIterator.h @@ -0,0 +1,14 @@ +#pragma once + +/** helper class to iterate all free clusters of the Filesystem */ +class FreeClusterIterator { + + FS& fs; + +public: + + FreeClusterIterator(FS& fs) : fs(fs) { + + } + +}; diff --git a/ext/sd/fat32/Structs.h b/ext/sd/fat32/Structs.h index 3cd3271..e28e5c7 100644 --- a/ext/sd/fat32/Structs.h +++ b/ext/sd/fat32/Structs.h @@ -1,13 +1,11 @@ #pragma once -#include #include +#include "Types.h" namespace FAT32 { using ClusterNr = uint32_t; - using AbsOffset = uint32_t; - using AbsPos = uint32_t; struct FSDesc { uint16_t bytesPerSector; @@ -23,7 +21,7 @@ namespace FAT32 { AbsPos startOfFAT; // absolute byte offset where the FAT begins AbsPos startOfFirstDataCluster; // absolute byte offset where the first cluster is AbsPos startOfFirstRootDirCluster; // absolute byte offset where the first root dir cluster is - uint32_t dirEntriesPerSector; + uint32_t dirEntriesPerSector; // number of directory entries that fit into a sector }; union Attributes { @@ -63,7 +61,7 @@ namespace FAT32 { uint8_t pos = 0; for (uint8_t i = 0; i < 8; ++i) {if (name[i] != ' ') {buf[pos++] = name[i];}} buf[pos++] = '.'; - for (uint8_t i = 0; i < 8; ++i) {if ( ext[i] != ' ') {buf[pos++] = ext[i];}} + for (uint8_t i = 0; i < 3; ++i) {if ( ext[i] != ' ') {buf[pos++] = ext[i];}} buf[pos] = 0; return std::string(buf); } diff --git a/ext/sd/fat32/WriteFile.h b/ext/sd/fat32/WriteFile.h new file mode 100644 index 0000000..91cafce --- /dev/null +++ b/ext/sd/fat32/WriteFile.h @@ -0,0 +1,5 @@ +#pragma once + +class WriteFile { + +}; diff --git a/ext/sd/main.cpp b/ext/sd/main.cpp index 8358304..6370ffa 100644 --- a/ext/sd/main.cpp +++ b/ext/sd/main.cpp @@ -1,64 +1,108 @@ #include #include +//#define debugMod(module, str) {printf("i[%-10s] ", module); printf(str); printf("\n");} +//#define debugMod1(module, str, v1) {printf("i[%-10s] ", module); printf(str, v1); printf("\n");} +//#define debugMod2(module, str, v1, v2) {printf("i[%-10s] ", module); printf(str, v1, v2); printf("\n");} +//#define debugMod3(module, str, v1, v2, v3) {printf("i[%-10s] ", module); printf(str, v1, v2, v3); printf("\n");} +//#define debugMod4(module, str, v1, v2, v3, v4) {printf("i[%-10s] ", module); printf(str, v1, v2, v3, v4); printf("\n");} +//#define debugMod5(module, str, v1, v2, v3, v4, v5) {printf("i[%-10s] ", module); printf(str, v1, v2, v3, v4, v5); printf("\n");} + +#define PLATFORM DESKTOP + +#include "../../Debug.h" #include "MBR.h" #include "fat32/FS.h" - +#include "AccessHelper.h" + +#include + class Simu { + FILE* f; + public: Simu(const char* image) { - f = fopen(image, "rb"); + f = fopen(image, "rw"); if (!f) {throw std::runtime_error("failed to open");} } - uint32_t read(uint32_t addr, uint32_t size, uint8_t* dst) { - fseek(f, addr, SEEK_SET); - return fread(dst, size, 1, f) * size; +// uint32_t readSingleBlock(uint32_t addr, uint32_t size, uint8_t* dst) { +// debugMod2("SD", "read(%d @ %d)", size, addr); +// fseek(f, addr, SEEK_SET); +// return fread(dst, size, 1, f) * size; +// } + + uint32_t readSingleBlock(LBA512 addr, uint8_t* dst) { + Log::addInfo("SD", "read512(%d*512)", addr); + fseek(f, addr*512, SEEK_SET); + return fread(dst, 512, 1, f) * 512; + } + + uint32_t writeSingleBlock(LBA512 addr, const uint8_t* src) { + Log::addInfo("SD", "write512(%d*512)", addr); + fseek(f, addr*512, SEEK_SET); + return fwrite(src, 512, 1, f) * 512; } }; -int main(void) { +#include + +int main(int argc, char** argv) { + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); // diff /tmp/ram/TETRIS.GB /apps/workspace/gbemu/tests/tetris.gb Simu simu("/tmp/ram/1.dat"); + AccessHelper ah(simu); - MBR mbr(simu); - std::cout << mbr.isPresent() << std::endl; - std::cout << mbr.isValid() << std::endl; - + MBR> mbr(ah); + if (mbr.isPresent() && mbr.isValid()) { for (int i = 0; i < 4; ++i) { std::cout << (int) mbr.getPartition(i).getType() << " - " << mbr.getPartition(i).getFirstSector() << " - " << mbr.getPartition(i).getNumSectors() << std::endl; } - using FAT32FS = FAT32::FS; + using FAT32FS = FAT32::FS>; - FAT32FS fat(simu, mbr.getPartition(0).getFirstSector() * 512); - std::cout << "valid: " << fat.isValid() << std::endl; + FAT32FS fat(ah, mbr.getPartition(0).getFirstSector() * 512); + + auto callback = [] (const int percent) { + std::cout << percent << std::endl; + }; FAT32FS::DirIterator dir = fat.getRoot(); - while(dir.hasNext()) { - FAT32::DirEntry de = dir.next(); - std::cout << de.getName() << std::endl; - FAT32FS::File f = fat.open(de); + while(true) { - uint8_t* bufff = (uint8_t*) malloc(1024*1024); - uint32_t read = f.read(f.getSize(), bufff); + FAT32::DirEntry* de = dir.next(); + if (!de) {break;} + std::cout << de->getName() << std::endl; - std::string name = de.getName(); - std::ofstream out("/tmp/ram/" + name); - out.write((char*)bufff, read); - out.close(); + if (1==0) { + FAT32FS::File f = fat.open(*de); + + uint8_t* bufff = (uint8_t*) malloc(1024*1024); + uint32_t read = f.read(de->size, bufff, callback); + + std::string name = de->getName(); + std::ofstream out("/tmp/ram/" + name); + out.write((char*)bufff, read); + out.close(); + + free(bufff); + + break; + } } // diff /tmp/ram/TETRIS.GB /apps/workspace/gbemu/tests/tetris.gb - // diif /tmp/ram/KIRBY1.GB /apps/workspace/gbemu/tests/Kirby\'s\ Dream\ Land\ \(USA\,\ Europe\).gb + // diff /tmp/ram/KIRBY1.GB /apps/workspace/gbemu/tests/Kirby\'s\ Dream\ Land\ \(USA\,\ Europe\).gb } diff --git a/ext/sd/tests/Helper.h b/ext/sd/tests/Helper.h new file mode 100644 index 0000000..3c726cb --- /dev/null +++ b/ext/sd/tests/Helper.h @@ -0,0 +1,26 @@ +#pragma once + +#define PLATFORM DESKTOP + +#include "../Types.h" + +struct TestDevice { + + uint8_t buf[4096]; + + /** read a 512 byte block into dst */ + bool readSingleBlock(LBA512 addr, uint8_t* dst) { + memcpy(dst, buf+addr*512, 512); + return true; + } + + bool writeSingleBlock(LBA512 addr, uint8_t* src) { + memcpy(buf+addr*512, src, 512); + return true; + } + + void reset(uint8_t val) { + for (int i = 0; i < sizeof(buf); ++i) {buf[i] = val;} + } + +}; diff --git a/ext/sd/tests/TestAccessHelper.cpp b/ext/sd/tests/TestAccessHelper.cpp new file mode 100644 index 0000000..609bdf4 --- /dev/null +++ b/ext/sd/tests/TestAccessHelper.cpp @@ -0,0 +1,78 @@ +#include + +#include "Helper.h" + +#include "../AccessHelper.h" + + +/* +TEST (TestAccessHelper, read) { + + // read varying numbers of bytes at arbitrary locations + + TestDevice dev; + AccessHelper ah(dev); + for (size_t i = 0; i < sizeof(dev.buf); ++i) {dev.buf[i] = i;} + + uint8_t dst[32]; + + for (int bytesToRead = 1; bytesToRead < 1100; bytesToRead+=5) { + for (int startAddr = 0; startAddr < 1100; startAddr+=3) { + + const uint32_t read = ah.read(startAddr, bytesToRead, dst); + + // ensure correct response + ASSERT_EQ(bytesToRead, read); + + // ensure correct contents + for (int j = 0; j < bytesToRead; ++j) { + ASSERT_EQ((uint8_t)(startAddr+j), dst[j]); + } + + } + } + +} +*/ + +TEST (TestAccessHelper, write) { + + // write varying numbers of bytes at arbitrary locations + + const uint8_t MAGIC = 0xFF; + TestDevice dev; + AccessHelper ah(dev); + + // src data + uint8_t src[2048]; + for (uint32_t i = 0; i < 2048; ++i) {src[i] = i;} + + // try several write sizes and start addresses + for (uint32_t bytesToWrite = 255; bytesToWrite < 1100; bytesToWrite+=5) { + for (uint32_t startAddr = 0; startAddr < 1100; startAddr+=3) { + + dev.reset(MAGIC); + + const uint32_t written = ah.write(startAddr, bytesToWrite, src); + + // ensure correct response + ASSERT_EQ(bytesToWrite, written); + + // check content + for (uint32_t i = 0; i < sizeof(dev.buf); ++i) { + if (i < startAddr || i >= startAddr + bytesToWrite) { + ASSERT_EQ(MAGIC, dev.buf[i]); + } else { + const uint8_t expected = (i - startAddr); + ASSERT_EQ(expected, dev.buf[i]); + } + + } + + + } + } + + + +} diff --git a/io/SoftSPI.h b/io/SoftSPI.h index 564f356..c6b0ea1 100644 --- a/io/SoftSPI.h +++ b/io/SoftSPI.h @@ -28,7 +28,7 @@ public: private: void init() { - debugMod3(NAME, "init() MISO:%d MOSI:%d CLK:%d", PIN_MISO, PIN_MOSI, PIN_CLK); + Log::addInfo(NAME, "init() MISO:%d MOSI:%d CLK:%d", PIN_MISO, PIN_MOSI, PIN_CLK); if (PIN_MISO) {MyGPIO::setInput(PIN_MISO);} if (PIN_MOSI) {MyGPIO::setOutput(PIN_MOSI);} MyGPIO::setOutput(PIN_CLK);