worked on FileSystem, started to migrate logging class
This commit is contained in:
@@ -457,7 +457,7 @@ private:
|
||||
MyGPIO::clear(PIN_RESET);
|
||||
delay(250);
|
||||
MyGPIO::set(PIN_RESET);
|
||||
delay(400);
|
||||
delay(250);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
134
ext/sd/AccessHelper.h
Normal file
134
ext/sd/AccessHelper.h
Normal file
@@ -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 <typename Dev> 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;}
|
||||
|
||||
};
|
||||
73
ext/sd/CMakeLists.txt
Executable file
73
ext/sd/CMakeLists.txt
Executable file
@@ -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})
|
||||
|
||||
10
ext/sd/MBR.h
10
ext/sd/MBR.h
@@ -2,6 +2,8 @@
|
||||
|
||||
template <typename BlockDev> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 <typename SPI, int PIN_CS> 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;}
|
||||
}
|
||||
|
||||
8
ext/sd/Types.h
Normal file
8
ext/sd/Types.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
using LBA512 = uint32_t;
|
||||
|
||||
using AbsPos = uint32_t;
|
||||
using AbsOffset = uint32_t;
|
||||
@@ -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<DirEntry*>(buf + (sizeof(DirEntry) * curEntryInCluster));
|
||||
// the current entry
|
||||
DirEntry* dirEntry = reinterpret_cast<DirEntry*>(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<DirEntry*>(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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#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<uint8_t*>(&next));
|
||||
ClusterNr next = 0;
|
||||
dev.read(pos, 4, reinterpret_cast<uint8_t*>(&next));
|
||||
Log::addInfo(NAME, "nextCluster(%d) -> %d", clusterNr, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "Structs.h"
|
||||
|
||||
// https://www.pjrc.com/tech/8051/ide/fat32.html
|
||||
namespace FAT32 {
|
||||
|
||||
template <typename BlockDev> 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<uint8_t*>(&next));
|
||||
return next;
|
||||
}
|
||||
|
||||
/** convert ClusterNr into an absolute position on disk */
|
||||
AbsPos clusterToAbsPos(ClusterNr clusterNr) {
|
||||
return tmp.startOfFirstDataCluster + ((clusterNr - 2) * desc.sectorsPerCluster * desc.bytesPerSector);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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<void(int)> 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?
|
||||
|
||||
14
ext/sd/fat32/FreeClusterIterator.h
Normal file
14
ext/sd/fat32/FreeClusterIterator.h
Normal file
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,13 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#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);
|
||||
}
|
||||
|
||||
5
ext/sd/fat32/WriteFile.h
Normal file
5
ext/sd/fat32/WriteFile.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
class WriteFile {
|
||||
|
||||
};
|
||||
@@ -1,64 +1,108 @@
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
//#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 <fstream>
|
||||
|
||||
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 <gtest/gtest.h>
|
||||
|
||||
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<Simu> ah(simu);
|
||||
|
||||
MBR<Simu> mbr(simu);
|
||||
std::cout << mbr.isPresent() << std::endl;
|
||||
std::cout << mbr.isValid() << std::endl;
|
||||
|
||||
MBR<AccessHelper<Simu>> 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<Simu>;
|
||||
using FAT32FS = FAT32::FS<AccessHelper<Simu>>;
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
|
||||
26
ext/sd/tests/Helper.h
Normal file
26
ext/sd/tests/Helper.h
Normal file
@@ -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;}
|
||||
}
|
||||
|
||||
};
|
||||
78
ext/sd/tests/TestAccessHelper.cpp
Normal file
78
ext/sd/tests/TestAccessHelper.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Helper.h"
|
||||
|
||||
#include "../AccessHelper.h"
|
||||
|
||||
|
||||
/*
|
||||
TEST (TestAccessHelper, read) {
|
||||
|
||||
// read varying numbers of bytes at arbitrary locations
|
||||
|
||||
TestDevice dev;
|
||||
AccessHelper<TestDevice> 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<TestDevice> 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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user