Last active
October 7, 2022 09:27
-
-
Save gabonator/e93371f5d51a1af294f1413118942e1a to your computer and use it in GitHub Desktop.
sdcard driver for la104
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <library.h> | |
namespace Platform | |
{ | |
typedef int32_t time_t; | |
time_t millis() | |
{ | |
return BIOS::SYS::GetTick(); | |
} | |
void delay(int ms) | |
{ | |
BIOS::SYS::DelayMs(ms); | |
} | |
void log_w(const char* msg, ...) | |
{ | |
char buf[128]; | |
BIOS::DBG::Print("Warning: "); | |
va_list args; | |
va_start( args, msg ); | |
vsprintf( buf, msg, args ); | |
va_end(args); | |
BIOS::DBG::Print(buf); | |
BIOS::DBG::Print("\n"); | |
} | |
void log_e(const char* msg, ...) | |
{ | |
char buf[128]; | |
BIOS::DBG::Print("Error: "); | |
va_list args; | |
va_start( args, msg ); | |
vsprintf( buf, msg, args ); | |
va_end(args); | |
BIOS::DBG::Print(buf); | |
BIOS::DBG::Print("\n"); | |
} | |
void log_i(const char* msg, ...) | |
{ | |
char buf[128]; | |
BIOS::DBG::Print("Info: "); | |
va_list args; | |
va_start( args, msg ); | |
vsprintf( buf, msg, args ); | |
va_end(args); | |
BIOS::DBG::Print(buf); | |
BIOS::DBG::Print("\n"); | |
} | |
class CGpio | |
{ | |
public: | |
enum EPin {SCK, MOSI, MISO, CS}; | |
enum EDir {Input, Output}; | |
public: | |
BIOS::GPIO::EPin Pin(EPin p) const | |
{ | |
switch (p) | |
{ | |
case MOSI: return BIOS::GPIO::EPin::P1; | |
case SCK: return BIOS::GPIO::EPin::P2; | |
case CS: return BIOS::GPIO::EPin::P3; | |
case MISO: return BIOS::GPIO::EPin::P4; | |
default: | |
return BIOS::GPIO::EPin::P1; | |
} | |
} | |
void PinMode(EPin p, EDir d) const | |
{ | |
if (d == EDir::Input) | |
BIOS::GPIO::PinMode(Pin(p), BIOS::GPIO::EMode::Input); | |
else | |
BIOS::GPIO::PinMode(Pin(p), BIOS::GPIO::EMode::Output); | |
} | |
void Set(EPin p, bool b) const | |
{ | |
BIOS::GPIO::DigitalWrite(Pin(p), b); | |
} | |
bool Get(EPin p) const | |
{ | |
return BIOS::GPIO::DigitalRead(Pin(p)); | |
} | |
}; | |
} | |
class CSpi : public Platform::CGpio | |
{ | |
public: | |
void begin() const | |
{ | |
PinMode(CGpio::CS, CGpio::Output); | |
PinMode(CGpio::SCK, CGpio::Output); | |
PinMode(CGpio::MOSI, CGpio::Output); | |
PinMode(CGpio::MISO, CGpio::Input); | |
Set(CGpio::CS, true); | |
Set(CGpio::SCK, true); | |
Set(CGpio::MOSI, false); | |
deselect(); | |
} | |
void end() const | |
{ | |
PinMode(CGpio::CS, CGpio::Input); | |
PinMode(CGpio::SCK, CGpio::Input); | |
PinMode(CGpio::MOSI, CGpio::Input); | |
PinMode(CGpio::MISO, CGpio::Input); | |
} | |
uint8_t transfer(uint8_t b) const | |
{ | |
uint8_t dataIn; | |
transferBytes(&b, &dataIn, 1); | |
return dataIn; | |
} | |
uint16_t transfer16(uint16_t b) const | |
{ | |
uint8_t dataOut[2] = {(uint8_t)(b >> 8), (uint8_t)b}; | |
uint8_t dataIn[2]; | |
transferBytes(dataOut, dataIn, 2); | |
return dataIn[1] | (dataIn[0] << 8); | |
} | |
uint32_t transfer32(uint32_t b) const | |
{ | |
uint8_t dataOut[4] = {(uint8_t)(b >> 24), (uint8_t)(b >> 16), (uint8_t)(b >> 8), (uint8_t)b}; | |
uint8_t dataIn[4]; | |
transferBytes(dataOut, dataIn, 4); | |
return dataIn[3] | (dataIn[2] << 8) | (dataIn[1] << 16) | (dataIn[0] << 24); | |
} | |
void transferBytes(uint8_t* bufWrite, uint8_t* bufRead, int len) const | |
{ | |
while (len--) | |
{ | |
uint8_t snd = bufWrite ? *bufWrite++ : 0xff; | |
uint8_t rcv = 0; | |
for (int i = 0; i < 8; i++) | |
{ | |
rcv <<= 1; | |
Set(CGpio::MOSI, snd & 0x80); | |
Set(CGpio::SCK, true); | |
if (Get(CGpio::MISO)) | |
rcv |= 1; | |
Set(CGpio::SCK, false); | |
snd <<= 1; | |
} | |
if (bufRead) | |
*bufRead++ = rcv; | |
} | |
} | |
void select() const | |
{ | |
Set(CGpio::CS, false); | |
} | |
void deselect() const | |
{ | |
Set(CGpio::CS, true); | |
} | |
}; | |
class CSd | |
{ | |
public: | |
typedef enum { | |
CARD_NONE, | |
CARD_MMC, | |
CARD_SD, | |
CARD_SDHC, | |
CARD_UNKNOWN | |
} sdcard_type_t; | |
public: | |
typedef enum { | |
GO_IDLE_STATE = 0, | |
SEND_OP_COND = 1, | |
SEND_CID = 2, | |
SEND_RELATIVE_ADDR = 3, | |
SEND_SWITCH_FUNC = 6, | |
SEND_IF_COND = 8, | |
SEND_CSD = 9, | |
STOP_TRANSMISSION = 12, | |
SEND_STATUS = 13, | |
SET_BLOCKLEN = 16, | |
READ_BLOCK_SINGLE = 17, | |
READ_BLOCK_MULTIPLE = 18, | |
SEND_NUM_WR_BLOCKS = 22, | |
SET_WR_BLK_ERASE_COUNT = 23, | |
WRITE_BLOCK_SINGLE = 24, | |
WRITE_BLOCK_MULTIPLE = 25, | |
APP_OP_COND = 41, | |
APP_CLR_CARD_DETECT = 42, | |
APP_CMD = 55, | |
READ_OCR = 58, | |
CRC_ON_OFF = 59 | |
} sdcard_command_t; | |
const CSpi& mSpi; | |
sdcard_type_t mType{CARD_NONE}; | |
bool mSupportsCrc{true}; | |
int mSectors{0}; | |
bool mConnected{false}; | |
public: | |
CSd(CSpi& spi) : mSpi(spi) | |
{ | |
} | |
bool wait(int timeout) | |
{ | |
char resp; | |
Platform::time_t start = Platform::millis(); | |
do { | |
resp = mSpi.transfer(0xff); | |
} while (resp == 0x00 && (Platform::millis() - start) < timeout); | |
if (!resp) | |
Platform::log_w("Wait Failed"); | |
return resp > 0x00; | |
} | |
bool selectCard() | |
{ | |
mSpi.select(); | |
bool s = wait(500); | |
if (!s) | |
{ | |
Platform::log_e("Select Failed"); | |
mSpi.deselect(); | |
return false; | |
} | |
return true; | |
} | |
void deselectCard() | |
{ | |
mSpi.deselect(); | |
} | |
uint8_t command(uint8_t cmd, uint32_t arg, uint32_t* resp) | |
{ | |
uint8_t token; | |
for (int f = 0; f < 3; f++) | |
{ | |
if (cmd == SEND_NUM_WR_BLOCKS || cmd == SET_WR_BLK_ERASE_COUNT || cmd == APP_OP_COND || cmd == APP_CLR_CARD_DETECT) | |
{ | |
token = command(APP_CMD, 0, nullptr); | |
deselectCard(); | |
if (token > 1) | |
break; | |
if(!selectCard()) | |
{ | |
token = 0xff; | |
break; | |
} | |
} | |
uint8_t cmdPacket[7]; | |
cmdPacket[0] = cmd | 0x40; | |
cmdPacket[1] = arg >> 24; | |
cmdPacket[2] = arg >> 16; | |
cmdPacket[3] = arg >> 8; | |
cmdPacket[4] = arg; | |
if(mSupportsCrc || cmd == GO_IDLE_STATE || cmd == SEND_IF_COND) | |
cmdPacket[5] = (CRC7(cmdPacket, 5) << 1) | 0x01; | |
else | |
cmdPacket[5] = 0x01; | |
cmdPacket[6] = 0xFF; | |
mSpi.transferBytes(cmdPacket, nullptr, (cmd == STOP_TRANSMISSION) ? 7 : 6); | |
for (int i = 0; i < 9; i++) | |
{ | |
token = mSpi.transfer(0xFF); | |
if (!(token & 0x80)) | |
break; | |
} | |
if (token == 0xff) | |
{ | |
Platform::log_w("no token received"); | |
deselectCard(); | |
Platform::delay(100); | |
selectCard(); | |
continue; | |
} else if (token & 0x08) | |
{ | |
Platform::log_w("crc error"); | |
deselectCard(); | |
Platform::delay(100); | |
selectCard(); | |
continue; | |
} else if (token > 1) | |
{ | |
Platform::log_w("token error [%d] 0x%x", cmd, token); | |
break; | |
} | |
if (cmd == SEND_STATUS && resp) | |
*resp = mSpi.transfer(0xff); | |
else if ((cmd == SEND_IF_COND || cmd == READ_OCR) && resp) | |
*resp = mSpi.transfer32(0xffffffff); | |
break; | |
} | |
if (token == 0xff) | |
{ | |
Platform::log_e("Card Failed! cmd: 0x%02x", cmd); | |
mConnected = false; | |
} | |
return token; | |
} | |
bool readSector(uint8_t* buffer, uint64_t sector) | |
{ | |
for (int f = 0; f < 3; f++) | |
{ | |
if(!selectCard()) | |
return false; | |
if (!command(READ_BLOCK_SINGLE, mType == CARD_SDHC ? sector : sector << 9, nullptr)) | |
{ | |
bool success = readBytes(buffer, 512); | |
deselectCard(); | |
if (success) | |
return true; | |
} else | |
break; | |
} | |
deselectCard(); | |
return false; | |
} | |
bool readBytes(uint8_t* buffer, int length) | |
{ | |
uint8_t token; | |
uint16_t crc; | |
Platform::time_t start = Platform::millis(); | |
do { | |
token = mSpi.transfer(0xff); | |
} while (token == 0xFF && (Platform::millis() - start) < 500); | |
if (token != 0xfe) | |
{ | |
Platform::log_w("gabo: sdreadbytes wrong token"); | |
return false; | |
} | |
mSpi.transferBytes(nullptr, buffer, length); | |
crc = mSpi.transfer16(0xffff); | |
return !mSupportsCrc || crc == CRC16(buffer, length); | |
} | |
uint64_t getSectorsCount() | |
{ | |
for (int f = 0; f < 3; f++) | |
{ | |
if(!selectCard()) | |
{ | |
Platform::log_w("gabo: getsect select fail"); | |
return false; | |
} | |
if (!command(SEND_CSD, 0, nullptr)) | |
{ | |
uint8_t csd[16]; | |
bool success = readBytes(csd, 16); | |
deselectCard(); | |
if (success) | |
{ | |
if ((csd[0] >> 6) == 0x01) | |
{ | |
uint64_t size = ( | |
((uint64_t)(csd[7] & 0x3F) << 16) | |
| ((uint64_t)csd[8] << 8) | |
| csd[9] | |
) + 1; | |
return size << 10; | |
} | |
uint64_t size = ( | |
((uint64_t)(csd[6] & 0x03) << 10) | |
| ((uint64_t)csd[7] << 2) | |
| ((csd[8] & 0xC0) >> 6) | |
) + 1; | |
size <<= (( | |
((csd[9] & 0x03) << 1) | |
| ((csd[10] & 0x80) >> 7) | |
) + 2); | |
size <<= (csd[5] & 0x0F); | |
return size >> 9; | |
} | |
} else | |
{ | |
break; | |
} | |
} | |
deselectCard(); | |
return 0; | |
} | |
uint8_t transaction(uint8_t cmd, uint32_t arg, uint32_t* resp) | |
{ | |
if(!selectCard()) | |
return 0xff; | |
uint8_t token = command(cmd, arg, resp); | |
deselectCard(); | |
return token; | |
} | |
bool init() | |
{ | |
uint8_t token; | |
uint32_t resp; | |
Platform::time_t start; | |
mSpi.begin(); | |
mSpi.deselect(); | |
for (uint8_t i = 0; i < 20; i++) | |
mSpi.transfer(0XFF); | |
// Fix mount issue - sdWait fail ignored before command GO_IDLE_STATE | |
mSpi.select(); | |
if(!wait(500)) | |
Platform::log_w("sdWait fail ignored, card initialize continues"); | |
if (command(GO_IDLE_STATE, 0, nullptr) != 1) | |
{ | |
deselectCard(); | |
Platform::log_w("GO_IDLE_STATE failed"); | |
goto unknown_card; | |
} | |
deselectCard(); | |
token = transaction(CRC_ON_OFF, 1, nullptr); | |
if (token == 0x5) | |
{ | |
//old card maybe | |
mSupportsCrc = false; | |
} else if (token != 1) | |
{ | |
Platform::log_w("CRC_ON_OFF failed %d", token); | |
goto unknown_card; | |
} | |
if (transaction(SEND_IF_COND, 0x1aa, &resp) == 1) | |
{ | |
if ((resp & 0xfff) != 0x1aa) | |
{ | |
Platform::log_w("SEND_IF_COND failed: %03X", resp & 0xfff); | |
goto unknown_card; | |
} | |
if (transaction(READ_OCR, 0, &resp) != 1 || !(resp & (1 << 20))) | |
{ | |
Platform::log_w("READ_OCR failed: %X", resp); | |
goto unknown_card; | |
} | |
start = Platform::millis(); | |
do { | |
token = transaction(APP_OP_COND, 0x40100000, nullptr); | |
} while (token == 1 && (Platform::millis() - start) < 1000); | |
if (token) | |
{ | |
Platform::log_w("APP_OP_COND failed: %u", token); | |
goto unknown_card; | |
} | |
if (!transaction(READ_OCR, 0, &resp)) | |
{ | |
if (resp & (1 << 30)) | |
mType = CARD_SDHC; | |
else | |
mType = CARD_SD; | |
} else | |
{ | |
Platform::log_w("READ_OCR failed: %X", resp); | |
goto unknown_card; | |
} | |
} else { | |
if (transaction(READ_OCR, 0, &resp) != 1 || !(resp & (1 << 20))) | |
{ | |
Platform::log_w("READ_OCR failed: %X", resp); | |
goto unknown_card; | |
} | |
start = Platform::millis(); | |
do { | |
token = transaction(APP_OP_COND, 0x100000, nullptr); | |
} while (token == 0x01 && (Platform::millis() - start) < 1000); | |
if (!token) | |
{ | |
mType = CARD_SD; | |
} else | |
{ | |
start = Platform::millis(); | |
do { | |
token = transaction(SEND_OP_COND, 0x100000, nullptr); | |
} while (token != 0x00 && (Platform::millis() - start) < 1000); | |
if (token == 0x00) | |
{ | |
mType = CARD_MMC; | |
} else | |
{ | |
Platform::log_w("SEND_OP_COND failed: %u", token); | |
goto unknown_card; | |
} | |
} | |
} | |
if (mType != CARD_MMC) | |
{ | |
if (transaction(APP_CLR_CARD_DETECT, 0, nullptr)) | |
{ | |
Platform::log_w("APP_CLR_CARD_DETECT failed"); | |
goto unknown_card; | |
} | |
} | |
if (mType != CARD_SDHC) | |
{ | |
if (transaction(SET_BLOCKLEN, 512, nullptr) != 0x00) | |
{ | |
Platform::log_w("SET_BLOCKLEN failed"); | |
goto unknown_card; | |
} | |
} | |
mSectors = getSectorsCount(); | |
return true; | |
unknown_card: | |
mType = CARD_UNKNOWN; | |
return false; | |
} | |
private: | |
static uint8_t m_CRC7Table[]; | |
static uint16_t m_CRC16Table[256]; | |
uint8_t CRC7(const uint8_t* data, int length) | |
{ | |
uint8_t crc = 0; | |
for (int i = 0; i < length; i++) | |
crc = m_CRC7Table[(crc << 1) ^ data[i]]; | |
return crc; | |
} | |
uint16_t CRC16(uint8_t* data, int length) | |
{ | |
uint16_t crc = 0; | |
for (int i = 0; i < length; i++) | |
crc = (crc << 8) ^ m_CRC16Table[((crc >> 8) ^ data[i]) & 0x00FF]; | |
return crc; | |
} | |
}; | |
uint8_t CSd::m_CRC7Table[] = { | |
0x00, 0x09, 0x12, 0x1B, 0x24, 0x2D, 0x36, 0x3F, | |
0x48, 0x41, 0x5A, 0x53, 0x6C, 0x65, 0x7E, 0x77, | |
0x19, 0x10, 0x0B, 0x02, 0x3D, 0x34, 0x2F, 0x26, | |
0x51, 0x58, 0x43, 0x4A, 0x75, 0x7C, 0x67, 0x6E, | |
0x32, 0x3B, 0x20, 0x29, 0x16, 0x1F, 0x04, 0x0D, | |
0x7A, 0x73, 0x68, 0x61, 0x5E, 0x57, 0x4C, 0x45, | |
0x2B, 0x22, 0x39, 0x30, 0x0F, 0x06, 0x1D, 0x14, | |
0x63, 0x6A, 0x71, 0x78, 0x47, 0x4E, 0x55, 0x5C, | |
0x64, 0x6D, 0x76, 0x7F, 0x40, 0x49, 0x52, 0x5B, | |
0x2C, 0x25, 0x3E, 0x37, 0x08, 0x01, 0x1A, 0x13, | |
0x7D, 0x74, 0x6F, 0x66, 0x59, 0x50, 0x4B, 0x42, | |
0x35, 0x3C, 0x27, 0x2E, 0x11, 0x18, 0x03, 0x0A, | |
0x56, 0x5F, 0x44, 0x4D, 0x72, 0x7B, 0x60, 0x69, | |
0x1E, 0x17, 0x0C, 0x05, 0x3A, 0x33, 0x28, 0x21, | |
0x4F, 0x46, 0x5D, 0x54, 0x6B, 0x62, 0x79, 0x70, | |
0x07, 0x0E, 0x15, 0x1C, 0x23, 0x2A, 0x31, 0x38, | |
0x41, 0x48, 0x53, 0x5A, 0x65, 0x6C, 0x77, 0x7E, | |
0x09, 0x00, 0x1B, 0x12, 0x2D, 0x24, 0x3F, 0x36, | |
0x58, 0x51, 0x4A, 0x43, 0x7C, 0x75, 0x6E, 0x67, | |
0x10, 0x19, 0x02, 0x0B, 0x34, 0x3D, 0x26, 0x2F, | |
0x73, 0x7A, 0x61, 0x68, 0x57, 0x5E, 0x45, 0x4C, | |
0x3B, 0x32, 0x29, 0x20, 0x1F, 0x16, 0x0D, 0x04, | |
0x6A, 0x63, 0x78, 0x71, 0x4E, 0x47, 0x5C, 0x55, | |
0x22, 0x2B, 0x30, 0x39, 0x06, 0x0F, 0x14, 0x1D, | |
0x25, 0x2C, 0x37, 0x3E, 0x01, 0x08, 0x13, 0x1A, | |
0x6D, 0x64, 0x7F, 0x76, 0x49, 0x40, 0x5B, 0x52, | |
0x3C, 0x35, 0x2E, 0x27, 0x18, 0x11, 0x0A, 0x03, | |
0x74, 0x7D, 0x66, 0x6F, 0x50, 0x59, 0x42, 0x4B, | |
0x17, 0x1E, 0x05, 0x0C, 0x33, 0x3A, 0x21, 0x28, | |
0x5F, 0x56, 0x4D, 0x44, 0x7B, 0x72, 0x69, 0x60, | |
0x0E, 0x07, 0x1C, 0x15, 0x2A, 0x23, 0x38, 0x31, | |
0x46, 0x4F, 0x54, 0x5D, 0x62, 0x6B, 0x70, 0x79 | |
}; | |
uint16_t CSd::m_CRC16Table[256] = { | |
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, | |
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, | |
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, | |
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, | |
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, | |
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, | |
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, | |
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, | |
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, | |
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, | |
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, | |
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, | |
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, | |
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, | |
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, | |
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, | |
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, | |
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, | |
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, | |
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, | |
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, | |
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, | |
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, | |
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, | |
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, | |
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, | |
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, | |
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, | |
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, | |
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, | |
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, | |
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 | |
}; | |
class CParser | |
{ | |
const uint8_t* mBuffer; | |
public: | |
CParser(uint8_t* buffer) : mBuffer(buffer) | |
{ | |
} | |
template <typename T> CParser& operator >> (T& arg) | |
{ | |
memcpy(&arg, mBuffer, sizeof(arg)); | |
mBuffer += sizeof(arg); | |
return *this; | |
} | |
}; | |
class CFat32 | |
{ | |
private: | |
CSd& mSd; | |
uint8_t* mData; | |
int mFirstDataSector; | |
int mPartitionBegin; | |
struct mbr_t | |
{ | |
enum PartitionType { | |
Fat12 = 0x01, | |
Fat16 = 0x06, | |
Fat32 = 0x0b | |
}; | |
struct { | |
uint8_t State; | |
uint8_t BeginHead; | |
uint16_t BeginSector; | |
uint8_t Type; | |
uint8_t EndHead; | |
uint16_t EndSector; | |
uint32_t FirstSectorOffset; | |
uint32_t NumSectors; | |
} partition1; | |
uint16_t signature; | |
bool Load(uint8_t* buffer) | |
{ | |
CParser(buffer+0x1fe) >> signature; | |
if (signature != 0xaa55) | |
{ | |
Platform::log_e("Mbr signature wrong %04x != aa55", signature); | |
return false; | |
} | |
CParser(buffer+0x1be) >> partition1.State | |
>> partition1.BeginHead >> partition1.BeginSector | |
>> partition1.Type | |
>> partition1.EndHead >> partition1.EndSector | |
>> partition1.FirstSectorOffset >> partition1.NumSectors; | |
if (partition1.Type != Fat32) | |
{ | |
Platform::log_e("Mbr wrong partition type %02x != Fat32(0x0b)", partition1.Type); | |
return false; | |
} | |
return true; | |
} | |
}; | |
struct bpb_t | |
{ | |
uint8_t JumpBoot[3]; | |
uint8_t OEMName[8]; | |
uint16_t BytesPerSector; | |
uint8_t SectorPerCluster; | |
uint16_t ReservedSectorCount; | |
uint8_t NumberOfFats; | |
uint16_t RootEntCnt; | |
uint16_t ToSec16; | |
uint8_t Media; | |
uint16_t FTASz16; | |
uint16_t SecPerTrk; | |
uint16_t NumHeads; | |
uint32_t HiddenSector; | |
uint32_t ToSec32; | |
uint32_t FATSz32; | |
uint16_t ExtFlags; | |
uint16_t FSVer; | |
uint32_t RootClus; | |
uint16_t FSInfo; | |
uint16_t BkBootSec; | |
uint8_t FatCal; | |
bool Load(uint8_t* buffer) | |
{ | |
CParser(buffer) >> JumpBoot >> OEMName >> BytesPerSector >> SectorPerCluster | |
>> ReservedSectorCount >> NumberOfFats >> RootEntCnt >> ToSec16 | |
>> Media >> FTASz16 >> SecPerTrk >> NumHeads >> HiddenSector >> ToSec32 | |
>> FATSz32 >> ExtFlags >> FSVer >> RootClus >> FSInfo >> BkBootSec >> FatCal; | |
Platform::log_i("bytes per sector %d", BytesPerSector); | |
Platform::log_i("sector per cluster %d", SectorPerCluster); | |
Platform::log_i("reserved sec count %d", ReservedSectorCount); | |
Platform::log_i("number of fats %d", NumberOfFats); | |
Platform::log_i("Rootentcnt %d", RootEntCnt); | |
Platform::log_i("Root cluster %u", RootClus); | |
Platform::log_i("BPB To Sec 32 %u", ToSec32); | |
Platform::log_i("size of each fat %u", FATSz32); | |
if (BytesPerSector != 512 || NumberOfFats != 2 || FATSz32 == 0) | |
return false; | |
return true; | |
} | |
}; | |
public: | |
struct direntry_t | |
{ | |
char Name[11]; | |
uint8_t Attr; | |
uint8_t NTRes; | |
uint8_t CrtTimeTenth; | |
uint16_t CrtTime; | |
uint16_t CrtDate; | |
uint16_t LstAccDate; | |
uint16_t FstClusHI; | |
uint16_t WrtTime; | |
uint16_t WrtDate; | |
uint16_t FstClusLO; | |
uint32_t FileSize; | |
// used during reading | |
int readSectors; | |
uint32_t readCluster; | |
bool Load(uint8_t* buffer) | |
{ | |
CParser(buffer) >> Name >> Attr >> NTRes | |
>> CrtTimeTenth >> CrtTime >> CrtDate | |
>> LstAccDate >> FstClusHI >> WrtTime | |
>> WrtDate >> FstClusLO >> FileSize; | |
return true; | |
} | |
bool IsDirectory() | |
{ | |
return (Name[0] != 0xe5) && (Attr & 0x10); | |
} | |
bool IsFile() | |
{ | |
return (Name[0] != 0xe5) && (Attr & 0x20); | |
} | |
void GetName(char* str) | |
{ | |
// name | |
int validNameChars; | |
for (validNameChars=7; validNameChars>=0; validNameChars--) | |
if (Name[validNameChars] != ' ' && Name[validNameChars]) | |
break; | |
for (int i=0; i<=validNameChars; i++) | |
{ | |
char c = Name[i]; | |
if (c >= 'A' && c <= 'Z') | |
c = c - 'A' + 'a'; | |
str[i] = c; | |
} | |
validNameChars++; | |
str[validNameChars++] = '.'; | |
str[validNameChars] = 0; | |
// extension | |
for (int i=0; i<3; i++) | |
{ | |
char c = Name[8+i]; | |
if (c == 0 || c == ' ') | |
continue; | |
if (c >= 'A' && c <= 'Z') | |
c = c - 'A' + 'a'; | |
str[validNameChars++] = c; | |
} | |
if (str[validNameChars-1] == '.') | |
str[validNameChars-1] = 0; | |
else | |
str[validNameChars] = 0; | |
} | |
}; | |
private: | |
bpb_t mBpb; | |
public: | |
CFat32(CSd& sd, uint8_t* sector) : mSd(sd), mData(sector) | |
{ | |
} | |
bool init() | |
{ | |
mSd.readSector(mData, 0); | |
mbr_t mbr; | |
if (!mbr.Load(mData)) | |
return false; | |
mPartitionBegin = mbr.partition1.FirstSectorOffset; | |
mSd.readSector(mData, mPartitionBegin); | |
if (!mBpb.Load(mData)) | |
return false; | |
uint32_t rootDirSectors = ((mBpb.RootEntCnt * 32) + (mBpb.BytesPerSector - 1)) / mBpb.BytesPerSector; | |
uint32_t FATSz = mBpb.FTASz16 != 0 ? mBpb.FTASz16 : mBpb.FATSz32; | |
mFirstDataSector = mBpb.ReservedSectorCount + mBpb.NumberOfFats * FATSz + rootDirSectors; | |
return true; | |
} | |
uint32_t NextCluster(uint32_t clusterNumber) | |
{ | |
uint32_t FatOffset = clusterNumber * 4; // fat32 | |
uint32_t ThisFatSecNum = mBpb.ReservedSectorCount + (FatOffset / mBpb.BytesPerSector); | |
uint32_t ThisFatEntOffset = FatOffset % mBpb.BytesPerSector; | |
mSd.readSector(mData, mPartitionBegin + ThisFatSecNum); | |
uint32_t cluster = 0; | |
cluster |= mData[ThisFatEntOffset]; | |
cluster |= mData[ThisFatEntOffset + 1] << 8; | |
cluster |= mData[ThisFatEntOffset + 2] << 16; | |
cluster |= mData[ThisFatEntOffset + 3] << 24; | |
cluster &= 0x0FFFFFFF; | |
return cluster; | |
} | |
bool FindFile(const char* name, direntry_t& dirEntry) | |
{ | |
uint32_t sector = mFirstDataSector; | |
uint32_t cluster = 2; | |
while (true) | |
{ | |
for (int j = 0; j < mBpb.SectorPerCluster; j++) | |
{ | |
mSd.readSector(mData, mPartitionBegin + sector + j); | |
for (int i = 0; i < 16; i++) | |
{ | |
dirEntry.Load(mData + i*32); | |
if (dirEntry.IsFile()) | |
{ | |
char fileName[16]; | |
dirEntry.GetName(fileName); | |
if (strcmp(name, fileName) == 0) | |
{ | |
dirEntry.readSectors = 0; | |
dirEntry.readCluster = (uint32_t)-1; | |
return true; | |
} | |
} | |
} | |
} | |
cluster = NextCluster(cluster); | |
if (cluster == 0x0FFFFFFF) | |
break; | |
if (cluster == 0) | |
{ | |
Platform::log_e("Wrong cluster"); | |
return false; | |
} | |
sector = (cluster - 2) * mBpb.SectorPerCluster + mFirstDataSector; | |
} | |
return false; | |
} | |
bool ReadFile(direntry_t& entry) | |
{ | |
if ((unsigned)entry.readSectors * mBpb.BytesPerSector >= entry.FileSize) | |
return false; | |
if ((entry.readSectors % mBpb.SectorPerCluster) == 0) | |
{ | |
if (entry.readCluster == (uint32_t)-1) | |
entry.readCluster = entry.FstClusLO | (entry.FstClusHI<<16); | |
else | |
entry.readCluster = NextCluster(entry.readCluster); | |
} | |
mSd.readSector(mData, mPartitionBegin + (entry.readCluster - 2)*mBpb.SectorPerCluster + mFirstDataSector + (entry.readSectors % mBpb.SectorPerCluster)); | |
entry.readSectors++; | |
return true; | |
} | |
void dump() | |
{ | |
uint32_t sector = mFirstDataSector; | |
uint32_t cluster = 2; | |
while (true) | |
{ | |
for (int j = 0; j < mBpb.SectorPerCluster; j++) | |
{ | |
mSd.readSector(mData, mPartitionBegin + sector + j); | |
for (int i = 0; i < 16; i++) | |
{ | |
direntry_t dirEntry; | |
dirEntry.Load(mData + i*32); | |
char fileName[16]; | |
dirEntry.GetName(fileName); | |
if (!strstr(fileName, ".txt")) | |
continue; | |
if (dirEntry.IsDirectory()) | |
Platform::log_i("<%s>, %x/%x ", fileName, | |
dirEntry.FstClusLO, dirEntry.FstClusHI); | |
else if (dirEntry.IsFile()) | |
Platform::log_i("%s, %d, %x/%x ", fileName, dirEntry.FileSize, | |
dirEntry.FstClusLO, dirEntry.FstClusHI); | |
} | |
} | |
cluster = NextCluster(cluster); | |
if (cluster == 0x0FFFFFFF) | |
break; | |
if (cluster == 0) | |
{ | |
Platform::log_e("Wrong cluster"); | |
return; | |
} | |
sector = (cluster - 2) * mBpb.SectorPerCluster + mFirstDataSector; | |
} | |
} | |
}; | |
uint8_t sector[512]; | |
__attribute__((__section__(".entry"))) | |
int main(void) | |
{ | |
CSpi spi; | |
CSd sd(spi); | |
CFat32 fat(sd, sector); | |
bool initStatus = sd.init(); | |
BIOS::DBG::Print("Init: %d, type: %d, sectors: %d\n", | |
initStatus, sd.mType, sd.mSectors); | |
if (!initStatus || sd.mType != 2 || sd.mSectors == 0) | |
{ | |
BIOS::DBG::Print("Exiting\n"); | |
BIOS::SYS::DelayMs(5000); | |
return 1; | |
} | |
if (!fat.init()) | |
{ | |
BIOS::DBG::Print("Exiting\n"); | |
BIOS::SYS::DelayMs(5000); | |
return 1; | |
} | |
fat.dump(); | |
CFat32::direntry_t entry; | |
if (fat.FindFile("short.txt", entry)) | |
{ | |
BIOS::DBG::Print("File contents (%d bytes):\n", entry.FileSize); | |
while (fat.ReadFile(entry)) | |
{ | |
sector[16] = 0; | |
BIOS::DBG::Print("%s...\n", sector); | |
} | |
BIOS::DBG::Print("Done\n"); | |
} else | |
BIOS::DBG::Print("Not found\n"); | |
if (fat.FindFile("long.txt", entry)) | |
{ | |
BIOS::DBG::Print("File contents (%d bytes):\n", entry.FileSize); | |
while (fat.ReadFile(entry)) | |
{ | |
sector[16] = 0; | |
BIOS::DBG::Print("%s...\n", sector); | |
} | |
BIOS::DBG::Print("Done\n"); | |
} else | |
BIOS::DBG::Print("Not found\n"); | |
if (fat.FindFile("longer.txt", entry)) | |
{ | |
BIOS::DBG::Print("File contents (%d bytes):\n", entry.FileSize); | |
while (fat.ReadFile(entry)) | |
{ | |
sector[16] = 0; | |
BIOS::DBG::Print("%s...\n", sector); | |
} | |
BIOS::DBG::Print("Done\n"); | |
} else | |
BIOS::DBG::Print("Not found\n"); | |
BIOS::KEY::EKey key = BIOS::KEY::None; | |
while ((key = BIOS::KEY::GetKey()) != BIOS::KEY::Escape) | |
{ | |
} | |
spi.end(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment