Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
a37949c2aThis file is pinned to the selected release. Download the original to open it in your design tool.
#include "../Marlin/src/sd/cardreader.h"
#include "bsod.h"
#include "dbg.h"
#include "ff.h"
#include "ffconf.h"
#include <stdbool.h>
#include <string.h>
#define DBG _dbg
//
// cardreader's `inserted` Status
//
static bool _media_inserted = false;
extern "C" bool media_is_inserted() {
return _media_inserted;
}
extern "C" void media_set_inserted(bool inserted) {
_media_inserted = inserted;
CardReader::flag.mounted = true;
}
//
// Utility functions
//
/// Get a DOS 8.3 filename in its useful form
static char *createFilename(char *const buffer, const dir_t &p) {
char *pos = buffer;
for (uint8_t i = 0; i < 11; i++) {
if (p.name[i] == ' ')
continue;
if (i == 8)
*pos++ = '.';
*pos++ = p.name[i];
}
*pos++ = 0;
return buffer;
}
//
// CardReader class implementation
//
card_flags_t CardReader::flag;
char CardReader::filename[FILENAME_LENGTH];
char CardReader::longFilename[LONG_FILENAME_LENGTH];
SdFile CardReader::file;
SdVolume CardReader::volume;
Sd2Card CardReader::sd2card;
uint32_t CardReader::filesize;
uint32_t CardReader::sdpos;
int8_t CardReader::autostart_index;
SdFile CardReader::root, CardReader::workDir,
CardReader::workDirParents[MAX_DIR_DEPTH];
uint8_t CardReader::workDirDepth;
uint8_t CardReader::file_subcall_ctr;
uint32_t CardReader::filespos[SD_PROCEDURE_DEPTH];
char CardReader::proc_filenames[SD_PROCEDURE_DEPTH][MAXPATHNAMELENGTH];
CardReader::CardReader() {
workDir = root;
flag.sdprinting = flag.mounted = flag.saving = flag.logging = false;
filesize = sdpos = 0;
file_subcall_ctr = 0;
ZERO(workDirParents);
// Disable autostart until card is initialized
autostart_index = -1;
}
void CardReader::openLogFile(char *const path) {
DBG("NOT IMPLEMENTED: openLogFile(...)");
}
void CardReader::startFileprint() {
if (isMounted())
flag.sdprinting = true;
}
void CardReader::printingHasFinished() {
if (isFileOpen())
file.close();
if (file_subcall_ctr > 0) {
// Heading up to a parent file that called current as a procedure.
file_subcall_ctr--;
openFile(proc_filenames[file_subcall_ctr], true, true);
setIndex(filespos[file_subcall_ctr]);
startFileprint();
} else {
stopSDPrint();
}
}
void CardReader::stopSDPrint() {
flag.sdprinting = flag.abort_sd_printing = false;
if (isFileOpen())
file.close();
}
void CardReader::release() {
// Nothing to do here. Releasing is done automatically outside of Marlin.
}
void CardReader::mount() {
// Nothing to do here. We handle mounting automatically outside of Marlin.
}
void CardReader::getAbsFilename(char *dst) {
*dst++ = '/';
uint8_t cnt = 1;
auto appendAtom = [&](SdFile &file) {
file.getDosName(dst);
while (*dst && cnt < MAXPATHNAMELENGTH) {
dst++;
cnt++;
}
if (cnt < MAXPATHNAMELENGTH) {
*dst = '/';
dst++;
cnt++;
}
};
for (uint8_t i = 0; i < workDirDepth; i++) // Loop down to current work dir
appendAtom(workDirParents[i]);
if (cnt < MAXPATHNAMELENGTH -
(FILENAME_LENGTH)-1) { // Leave room for filename and nul
appendAtom(file);
--dst;
}
*dst = '\0';
}
/**
* Dive to the given DOS 8.3 file path, with optional echo of the dive paths.
*
* On exit, curDir contains an SdFile reference to the file's directory.
*
* Returns a pointer to the last segment (filename) of the given DOS 8.3 path.
*
* A nullptr result indicates an unrecoverable error.
*/
const char *CardReader::diveToFile(SdFile *&curDir, const char *const path,
const bool echo /*=false*/) {
// Track both parent and subfolder
static SdFile newDir1, newDir2;
SdFile *sub = &newDir1, *startDir;
const char *item_name_adr = path;
if (path[0] == '/') {
curDir = &root;
workDirDepth = 0;
item_name_adr++;
} else
curDir = &workDir;
startDir = curDir;
// Start dive
while (item_name_adr) {
// Find next sub
char *const name_end = strchr(item_name_adr, '/');
if (name_end <= item_name_adr)
break;
// Set subDirName
const uint8_t len = name_end - item_name_adr;
char dosSubdirname[len + 1];
strncpy(dosSubdirname, item_name_adr, len);
dosSubdirname[len] = 0;
if (echo)
SERIAL_ECHOLN(dosSubdirname);
// Open curDir
if (!sub->open(curDir, dosSubdirname, O_READ)) {
SERIAL_ECHOLNPAIR(MSG_SD_OPEN_FILE_FAIL, dosSubdirname, ".");
return nullptr;
}
// Close curDir if not at starting-point
if (curDir != startDir)
curDir->close();
// curDir now subDir
curDir = sub;
// Update workDirParents and workDirDepth
if (workDirDepth < MAX_DIR_DEPTH)
workDirParents[workDirDepth++] = *curDir;
// Point sub pointer to unused newDir
sub = (curDir != &newDir1) ? &newDir1 : &newDir2;
// item_name_adr point to next sub
item_name_adr = name_end + 1;
}
return item_name_adr;
}
void CardReader::openFile(char *const path, const bool read, bool subcall) {
if (!isMounted())
return;
uint8_t doing = 0;
if (isFileOpen()) { // Replacing current file or doing a subroutine
if (subcall) {
if (file_subcall_ctr > SD_PROCEDURE_DEPTH - 1) {
bsod("sub-gcode openning too many files");
return;
}
// Store current filename (based on workDirParents) and position
getAbsFilename(proc_filenames[file_subcall_ctr]);
filespos[file_subcall_ctr] = sdpos;
SERIAL_ECHO_START();
SERIAL_ECHOLNPAIR("SUBROUTINE CALL target:\"", path, "\" parent:\"",
proc_filenames[file_subcall_ctr], "\" pos",
sdpos);
file_subcall_ctr++;
} else {
doing = 1;
}
} else if (subcall) {
// Returning from a subcall?
SERIAL_ECHO_MSG("END SUBROUTINE");
} else {
// Opening fresh file
doing = 2;
file_subcall_ctr = 0; // Reset procedure depth in case user cancels
// print while in procedure
}
if (doing) {
SERIAL_ECHO_START();
SERIAL_ECHOPGM("Now ");
serialprintPGM(doing == 1 ? PSTR("doing") : PSTR("fresh"));
SERIAL_ECHOLNPAIR(" file: ", path);
}
stopSDPrint();
SdFile *curDir;
const char *const fname = diveToFile(curDir, path);
if (!fname)
return;
if (read) {
if (file.open(curDir, fname, O_READ)) {
filesize = file.fileSize();
sdpos = 0;
SERIAL_ECHOLNPAIR(MSG_SD_FILE_OPENED, fname, MSG_SD_SIZE, filesize);
SERIAL_ECHOLNPGM(MSG_SD_FILE_SELECTED);
selectFileByName(fname);
} else {
SERIAL_ECHOLNPAIR(MSG_SD_OPEN_FILE_FAIL, fname, ".");
}
} else {
// write
if (!file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)) {
SERIAL_ECHOLNPAIR(MSG_SD_OPEN_FILE_FAIL, fname, ".");
} else {
flag.saving = true;
selectFileByName(fname);
#if ENABLED(EMERGENCY_PARSER)
emergency_parser.disable();
#endif
SERIAL_ECHOLNPAIR(MSG_SD_WRITE_TO_FILE, fname);
}
}
}
void CardReader::selectFileByName(const char *const match) {
workDir.rewind();
selectByName(workDir, match);
}
//
// Return 'true' if the item is a folder or G-code file
//
bool CardReader::is_dir_or_gcode(const dir_t &p) {
uint8_t pn0 = p.name[0];
if (pn0 == DIR_NAME_FREE ||
pn0 == DIR_NAME_DELETED // Clear or Deleted entry
|| pn0 == '.' || longFilename[0] == '.' // Hidden file
|| !DIR_IS_FILE_OR_SUBDIR(&p) // Not a File or Directory
|| (p.attributes & DIR_ATT_HIDDEN) // Hidden by attribute
)
return false;
flag.filenameIsDir = DIR_IS_SUBDIR(&p); // We know it's a File or Folder
return (flag.filenameIsDir // All Directories are ok
|| (p.name[8] == 'G' &&
p.name[9] != '~') // Non-backup *.G* files are accepted
);
}
void CardReader::selectByName(SdFile dir, const char *const match) {
dir_t p;
for (uint8_t cnt = 0; dir.readDir(&p, longFilename) > 0; cnt++) {
if (is_dir_or_gcode(p)) {
createFilename(filename, p);
if (strcasecmp(match, filename) == 0)
return;
}
}
}
void CardReader::closefile(const bool store_location) {
DBG("NOT IMPLEMENTED: closeFile(...)");
}
void CardReader::removeFile(const char *const name) {
DBG("NOT IMPLEMENTED: removeFile(...)");
}
void CardReader::report_status() {
DBG("NOT IMPLEMENTED: report_status(...)");
}
void CardReader::printFilename() {
DBG("NOT IMPLEMENTED: printFilename(...)");
}
void CardReader::ls() {
DBG("NOT IMPLEMENTED: ls()");
}
void CardReader::beginautostart() {
DBG("NOT IMPLEMENTED: beginautostart()");
}
void CardReader::checkautostart() {
}
void CardReader::write_command(char *const buf) {
DBG("NOT IMPLEMENTED: write_command(...)");
}
//
// SdBaseFile class implementation
//
class Slot {
public:
enum Type : uint8_t {
File,
Directory,
};
Type type;
union {
FIL file;
DIR directory;
};
Slot() : type(File), file(), is_used(false) {
}
static Slot *allocate(Type type) {
for (int i = 0; i < _pool_size; i++) {
auto &slot = _pool[i];
if (slot.is_used)
continue;
slot.type = type;
return &slot;
}
return nullptr;
}
static void free(Slot *slot) {
slot->is_used = false;
}
private:
bool is_used;
static constexpr int _pool_size = 3;
static Slot _pool[];
};
Slot Slot::_pool[Slot::_pool_size] = {};
//disable warning in following hack
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
// hack - firstCluster is used to store Slot*
#define _slot_of(x) (*((Slot **)(&x->firstCluster_)))
static void fname_from_8_3(char *fn, char *fn83) {
char *p;
strncpy(fn, fn83, 11);
fn[11] = 0;
p = strchr(fn, ' ');
if (fn83[8] != ' ') {
if (p) {
*p = '.';
p++;
strncpy(p, fn83 + 8, 3);
p = strchr(p, ' ');
if (p)
*p = 0;
} else {
strncpy(fn + 9, fn83 + 8, 3);
fn[8] = '.';
}
} else {
*p = 0;
}
}
bool SdBaseFile::getDosName(char *const name) {
if (!isOpen())
return false;
if (isRoot()) {
name[0] = '/';
name[1] = '\0';
return true;
}
if (isDir()) {
DIR *pd = &_slot_of(this)->directory;
fname_from_8_3(name, (char *)pd->fn);
return true;
} else {
// TODO: fix
bsod("get dos name of a file");
}
return false;
}
int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
// TODO: cleanup (copied from previous implementation)
FRESULT res;
FILINFO nfo;
char *str;
#if _USE_LFN != 0
char *altname = nfo.altname;
char *fname = nfo.fname;
#else
char *altname = nfo.fname;
// char* fname = 0;
#endif
if (longFilename)
longFilename[0] = '\0';
while (1) {
res = f_readdir(&_slot_of(this)->directory, &nfo);
if (res != FR_OK)
return -1;
memset(dir->name, ' ', 11);
str = strchr(altname, '.');
if (str) {
strncpy(((char *)dir->name) + 0, altname, str - altname);
strncpy(((char *)dir->name) + 8, str + 1, 3);
} else {
strncpy(((char *)dir->name) + 0, altname, 8);
}
if (dir->name[0] == DIR_NAME_FREE)
return 0;
if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') {
// if (longFilename) longFilename[0] = '\0'; //
// Invalidate erased file long name, if any
continue;
}
#if _USE_LFN != 0
if (longFilename)
strncpy(longFilename, fname, _MAX_LFN);
#endif
dir->attributes = nfo.fattrib;
dir->lastWriteDate = nfo.fdate;
dir->lastWriteTime = nfo.ftime;
dir->fileSize = nfo.fsize;
return sizeof(dir_t);
}
return 0;
}
bool SdBaseFile::seekSet(const uint32_t pos) {
if ((pos == 0) &&
((type_ == FAT_FILE_TYPE_ROOT_FIXED) ||
(type_ == FAT_FILE_TYPE_ROOT32) || (type_ == FAT_FILE_TYPE_SUBDIR))) {
return f_rewinddir(&_slot_of(this)->directory) == FR_OK;
}
if (type_ != FAT_FILE_TYPE_NORMAL)
return false;
FSIZE_t szf = pos;
if (f_lseek(&_slot_of(this)->file, szf) != FR_OK)
return false;
curPosition_ = pos;
return true;
}
int16_t SdBaseFile::read(void *buf, uint16_t nbyte) {
DBG("NOT IMPLEMENTED: read(...)");
return 0;
}
int16_t SdBaseFile::read() {
if (type_ != FAT_FILE_TYPE_NORMAL)
return -1;
uint8_t b;
UINT c;
if (f_read(&_slot_of(this)->file, &b, 1, &c) != FR_OK)
return -1;
curPosition_++;
return b;
}
bool SdBaseFile::close() {
Slot *slot;
bool success = false;
if (!isOpen())
return false;
switch (type_) {
case FAT_FILE_TYPE_NORMAL:
slot = _slot_of(this);
success = f_close(&slot->file) == FR_OK;
Slot::free(slot);
_slot_of(this) = nullptr;
break;
case FAT_FILE_TYPE_ROOT_FIXED:
case FAT_FILE_TYPE_ROOT32:
case FAT_FILE_TYPE_SUBDIR:
slot = _slot_of(this);
success = f_closedir(&slot->directory) == FR_OK;
Slot::free(slot);
_slot_of(this) = nullptr;
break;
default:
return false;
}
type_ = FAT_FILE_TYPE_CLOSED;
return success;
}
bool SdBaseFile::open(SdBaseFile *dir, const char *path, uint8_t oflag) {
if (!dir || isOpen())
return false;
// prepare full path
char fullpath[256] = {0};
if (dir->isRoot()) {
strcat(fullpath, "/");
strcat(fullpath, path);
} else {
auto dirp = &(_slot_of(dir)->directory);
f_getdirpath(dirp, fullpath, sizeof(fullpath));
strcat(fullpath, "/");
strcat(fullpath, path);
}
// get file info
FILINFO nfo;
if (f_stat(fullpath, &nfo) != FR_OK)
return false;
if (nfo.fattrib & AM_DIR) {
auto slot = Slot::allocate(Slot::Directory);
if (!slot)
return false;
if (f_opendir(&slot->directory, path) == FR_OK) {
_slot_of(this) = slot;
type_ = FAT_FILE_TYPE_SUBDIR;
curPosition_ = 0;
fileSize_ = f_size(&slot->directory);
return true;
} else {
Slot::free(slot);
return false;
}
} else {
auto slot = Slot::allocate(Slot::File);
if (!slot)
return false;
BYTE mode = 0;
if (oflag & O_READ)
mode |= FA_READ;
if (oflag & O_WRITE)
mode |= FA_WRITE;
if (f_open(&slot->file, fullpath, mode) == FR_OK) {
_slot_of(this) = slot;
type_ = FAT_FILE_TYPE_NORMAL;
curPosition_ = 0;
fileSize_ = f_size(&slot->file);
return true;
} else {
Slot::free(slot);
return false;
}
}
return true;
}
//
// Sd2Card class implementation
//
bool Sd2Card::isInserted() {
return media_is_inserted();
}
void Sd2Card::idle() {
}
//restore state - enable warnings
#pragma GCC diagnostic pop