diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c index b92b72df11..5a7235f809 100644 --- a/src/main/blackbox/blackbox.c +++ b/src/main/blackbox/blackbox.c @@ -258,6 +258,7 @@ static const blackboxSimpleFieldDefinition_t blackboxSlowFields[] = { typedef enum BlackboxState { BLACKBOX_STATE_DISABLED = 0, BLACKBOX_STATE_STOPPED, + BLACKBOX_STATE_PREPARE_LOG_FILE, BLACKBOX_STATE_SEND_HEADER, BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER, BLACKBOX_STATE_SEND_GPS_H_HEADER, @@ -479,7 +480,6 @@ static void blackboxSetState(BlackboxState newState) break; case BLACKBOX_STATE_SHUTTING_DOWN: xmitState.u.startTime = millis(); - blackboxDeviceEndLog(); break; default: ; @@ -787,8 +787,20 @@ static void validateBlackboxConfig() masterConfig.blackbox_rate_denom /= div; } - if (masterConfig.blackbox_device >= BLACKBOX_DEVICE_END) { - masterConfig.blackbox_device = BLACKBOX_DEVICE_SERIAL; + // If we've chosen an unsupported device, change the device to serial + switch (masterConfig.blackbox_device) { +#ifdef USE_FLASHFS + case BLACKBOX_DEVICE_FLASH: +#endif +#ifdef USE_SDCARD + case BLACKBOX_DEVICE_SDCARD: +#endif + case BLACKBOX_DEVICE_SERIAL: + // Device supported, leave the setting alone + break; + + default: + masterConfig.blackbox_device = BLACKBOX_DEVICE_SERIAL; } } @@ -834,7 +846,7 @@ void startBlackbox(void) */ blackboxLastArmingBeep = getArmingBeepTimeMicros(); - blackboxSetState(BLACKBOX_STATE_SEND_HEADER); + blackboxSetState(BLACKBOX_STATE_PREPARE_LOG_FILE); } } @@ -1301,6 +1313,11 @@ void handleBlackbox(void) } switch (blackboxState) { + case BLACKBOX_STATE_PREPARE_LOG_FILE: + if (blackboxDeviceBeginLog()) { + blackboxSetState(BLACKBOX_STATE_SEND_HEADER); + } + break; case BLACKBOX_STATE_SEND_HEADER: //On entry of this state, xmitState.headerIndex is 0 and startTime is intialised @@ -1409,7 +1426,7 @@ void handleBlackbox(void) * * Don't wait longer than it could possibly take if something funky happens. */ - if (millis() > xmitState.u.startTime + BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS || blackboxDeviceFlush()) { + if (blackboxDeviceEndLog() && (millis() > xmitState.u.startTime + BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS || blackboxDeviceFlush())) { blackboxDeviceClose(); blackboxSetState(BLACKBOX_STATE_STOPPED); } diff --git a/src/main/blackbox/blackbox_io.c b/src/main/blackbox/blackbox_io.c index a09f3c0a0d..6bd662c18a 100644 --- a/src/main/blackbox/blackbox_io.c +++ b/src/main/blackbox/blackbox_io.c @@ -77,7 +77,20 @@ static portSharing_e blackboxPortSharing; #ifdef USE_SDCARD -static afatfsFilePtr_t sdFile; +static struct { + afatfsFilePtr_t logFile; + afatfsFilePtr_t logDirectory; + afatfsFinder_t logDirectoryFinder; + uint32_t largestLogFileNumber; + + enum { + BLACKBOX_SDCARD_INITIAL, + BLACKBOX_SDCARD_WAITING, + BLACKBOX_SDCARD_ENUMERATE_FILES, + BLACKBOX_SDCARD_READY_TO_CREATE_LOG, + BLACKBOX_SDCARD_READY_TO_LOG + } state; +} blackboxSDCard; #endif @@ -91,7 +104,7 @@ void blackboxWrite(uint8_t value) #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: - afatfs_fputc(sdFile, value); + afatfs_fputc(blackboxSDCard.logFile, value); break; #endif case BLACKBOX_DEVICE_SERIAL: @@ -167,7 +180,7 @@ int blackboxPrint(const char *s) #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: length = strlen(s); - afatfs_fwrite(sdFile, (const uint8_t*) s, length); // Ignore failures due to buffers filling up + afatfs_fwrite(blackboxSDCard.logFile, (const uint8_t*) s, length); // Ignore failures due to buffers filling up break; #endif @@ -577,7 +590,7 @@ bool blackboxDeviceOpen(void) #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: - if (afatfs_getFilesystemState() != AFATFS_FILESYSTEM_STATE_READY || afatfs_isFull()) { + if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL || afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN || afatfs_isFull()) { return false; } @@ -598,6 +611,7 @@ void blackboxDeviceClose(void) { switch (masterConfig.blackbox_device) { case BLACKBOX_DEVICE_SERIAL: + // Since the serial port could be shared with other processes, we have to give it back here closeSerialPort(blackboxPort); blackboxPort = NULL; @@ -608,34 +622,163 @@ void blackboxDeviceClose(void) if (blackboxPortSharing == PORTSHARING_SHARED) { mspAllocateSerialPorts(&masterConfig.serialConfig); } - break; -#ifdef USE_FLASHFS - case BLACKBOX_DEVICE_FLASH: - // No-op since the flash doesn't have a "close" and there's nobody else to hand control of it to. - break; -#endif -#ifdef USE_SDCARD - case BLACKBOX_DEVICE_SDCARD: - afatfs_fclose(sdFile, NULL); - sdFile = NULL; - break; -#endif + break; + default: + ; } } +static void blackboxLogDirCreated(afatfsFilePtr_t directory) +{ + blackboxSDCard.logDirectory = directory; + + afatfs_findFirst(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder); + + blackboxSDCard.state = BLACKBOX_SDCARD_ENUMERATE_FILES; +} + +static void blackboxLogFileCreated(afatfsFilePtr_t file) +{ + blackboxSDCard.logFile = file; + + blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_LOG; +} + +static void blackboxCreateLogFile() +{ + blackboxSDCard.largestLogFileNumber++; + + uint32_t remainder = blackboxSDCard.largestLogFileNumber; + + char filename[13]; + + filename[0] = 'L'; + filename[1] = 'O'; + filename[2] = 'G'; + + for (int i = 7; i >= 3; i--) { + filename[i] = (remainder % 10) + '0'; + remainder /= 10; + } + + filename[8] = '.'; + filename[9] = 'T'; + filename[10] = 'X'; + filename[11] = 'T'; + filename[12] = 0; + + blackboxSDCard.state = BLACKBOX_SDCARD_WAITING; + + afatfs_fopen(filename, "as", blackboxLogFileCreated); +} + /** - * Terminate the current log (for devices which support separations between the logs of multiple flights) + * Begin a new log on the SDCard. + * + * Keep calling until the function returns true (open is complete). */ -void blackboxDeviceEndLog(void) +static bool blackboxSDCardBeginLog() +{ + fatDirectoryEntry_t *directoryEntry; + + doMore: + switch (blackboxSDCard.state) { + case BLACKBOX_SDCARD_INITIAL: + if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY) { + blackboxSDCard.state = BLACKBOX_SDCARD_WAITING; + + afatfs_mkdir("logs", blackboxLogDirCreated); + } + + return false; + + case BLACKBOX_SDCARD_WAITING: + // Waiting for directory entry to be created + return false; + + case BLACKBOX_SDCARD_ENUMERATE_FILES: + while (afatfs_findNext(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder, &directoryEntry) == AFATFS_OPERATION_SUCCESS) { + if (directoryEntry && !fat_isDirectoryEntryTerminator(directoryEntry)) { + // If this is a log file, parse the log number from the filename + if ( + directoryEntry->filename[0] == 'L' && directoryEntry->filename[1] == 'O' && directoryEntry->filename[2] == 'G' + && directoryEntry->filename[8] == 'T' && directoryEntry->filename[9] == 'X' && directoryEntry->filename[10] == 'T' + ) { + char logSequenceNumberString[6]; + + memcpy(logSequenceNumberString, directoryEntry->filename + 3, 5); + logSequenceNumberString[5] = '\0'; + + blackboxSDCard.largestLogFileNumber = atoi(logSequenceNumberString); + } + } else { + afatfs_findLast(blackboxSDCard.logDirectory); + + // We're done checking all the files on the card, now we can create a new log file + + // Change into the log directory: + afatfs_chdir(blackboxSDCard.logDirectory); + + // We no longer need our open handle on that Directory + afatfs_fclose(blackboxSDCard.logDirectory, NULL); + blackboxSDCard.logDirectory = NULL; + + blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; + goto doMore; + } + } + return false; + + case BLACKBOX_SDCARD_READY_TO_CREATE_LOG: + blackboxCreateLogFile(); + return false; + + case BLACKBOX_SDCARD_READY_TO_LOG: + return true; + } + + return false; +} + +/** + * Begin a new log (for devices which support separations between the logs of multiple flights). + * + * Keep calling until the function returns true (open is complete). + */ +bool blackboxDeviceBeginLog(void) { switch (masterConfig.blackbox_device) { #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: - if (afatfs_fclose(sdFile, NULL)) { - sdFile = NULL; - } - break; + return blackboxSDCardBeginLog(); #endif + default: + return true; + } + +} + +/** + * Terminate the current log (for devices which support separations between the logs of multiple flights). + * + * Keep calling until this returns true + */ +bool blackboxDeviceEndLog(void) +{ + switch (masterConfig.blackbox_device) { +#ifdef USE_SDCARD + case BLACKBOX_DEVICE_SDCARD: + // Keep retrying until the close operation queues + if (afatfs_fclose(blackboxSDCard.logFile, NULL)) { + // Don't bother waiting the for the close to complete, it's queued now and will complete eventually + blackboxSDCard.logFile = NULL; + blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; + return true; + } + return false; +#endif + default: + return true; } } diff --git a/src/main/blackbox/blackbox_io.h b/src/main/blackbox/blackbox_io.h index 2421c2c31f..08dc38b114 100644 --- a/src/main/blackbox/blackbox_io.h +++ b/src/main/blackbox/blackbox_io.h @@ -26,10 +26,10 @@ typedef enum BlackboxDevice { BLACKBOX_DEVICE_SERIAL = 0, #ifdef USE_FLASHFS - BLACKBOX_DEVICE_FLASH, + BLACKBOX_DEVICE_FLASH = 1, #endif #ifdef USE_SDCARD - BLACKBOX_DEVICE_SDCARD, + BLACKBOX_DEVICE_SDCARD = 2, #endif BLACKBOX_DEVICE_END @@ -75,7 +75,9 @@ void blackboxWriteFloat(float value); bool blackboxDeviceFlush(void); bool blackboxDeviceOpen(void); void blackboxDeviceClose(void); -void blackboxDeviceEndLog(void); + +bool blackboxDeviceBeginLog(void); +bool blackboxDeviceEndLog(void); bool isBlackboxDeviceFull(void); diff --git a/src/main/drivers/sdcard_standard.h b/src/main/drivers/sdcard_standard.h index 8b00e63453..ef69d3024c 100644 --- a/src/main/drivers/sdcard_standard.h +++ b/src/main/drivers/sdcard_standard.h @@ -207,18 +207,19 @@ typedef struct sdcardCSD_t { #define SDCARD_R1_STATUS_BIT_ADDRESS_ERROR 32 #define SDCARD_R1_STATUS_BIT_PARAMETER_ERROR 64 -#define SDCARD_CSD_STRUCTURE_VERSION_1 0 -#define SDCARD_CSD_STRUCTURE_VERSION_2 1 +#define SDCARD_CSD_STRUCTURE_VERSION_1 0 +#define SDCARD_CSD_STRUCTURE_VERSION_2 1 -#define SDCARD_VOLTAGE_ACCEPTED_2_7_to_3_6 0x01 -#define SDCARD_VOLTAGE_ACCEPTED_LVR 0x02 +#define SDCARD_VOLTAGE_ACCEPTED_2_7_to_3_6 0x01 +#define SDCARD_VOLTAGE_ACCEPTED_LVR 0x02 #define SDCARD_COMMAND_GO_IDLE_STATE 0 #define SDCARD_COMMAND_SEND_OP_COND 1 -#define SDCARD_COMMAND_SEND_IF_COND 8 +#define SDCARD_COMMAND_SEND_IF_COND 8 #define SDCARD_COMMAND_SEND_CSD 9 #define SDCARD_COMMAND_SEND_CID 10 #define SDCARD_COMMAND_STOP_TRANSMISSION 12 +#define SDCARD_COMMAND_SEND_STATUS 13 #define SDCARD_COMMAND_SET_BLOCKLEN 16 #define SDCARD_COMMAND_READ_SINGLE_BLOCK 17 #define SDCARD_COMMAND_READ_MULTIPLE_BLOCK 18 diff --git a/src/main/io/asyncfatfs/asyncfatfs.c b/src/main/io/asyncfatfs/asyncfatfs.c index d3b345e055..0c8ce007b9 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.c +++ b/src/main/io/asyncfatfs/asyncfatfs.c @@ -129,6 +129,9 @@ typedef struct afatfsCacheBlockDescriptor_t { /* * The state of this block must not transition (do not flush to disk, do not discard). This is useful for a sector * which is currently being written to by the application (so flushing it would be a waste of time). + * + * This is a binary state rather than a counter because we assume that only one file will be responsible for the + * sectors it locks. */ unsigned locked:1; @@ -312,7 +315,9 @@ typedef struct afatfsFile_t { * seek across a sector boundary. This allows fwrite() to complete faster because it doesn't need to check the * cache on every call. */ - int8_t lockedCacheIndex; + int8_t writeLockedCacheIndex; + // Ditto for fread(): + int8_t readRetainCacheIndex; // The position of our directory entry on the disk (so we can update it without consulting a parent directory file) afatfsDirEntryPointer_t directoryEntryPos; @@ -392,7 +397,7 @@ static afatfs_t afatfs; static void afatfs_fileOperationContinue(afatfsFile_t *file); static uint8_t* afatfs_fileLockCursorSectorForWrite(afatfsFilePtr_t file); -static uint8_t* afatfs_fileGetCursorSectorForRead(afatfsFilePtr_t file); +static uint8_t* afatfs_fileRetainCursorSectorForRead(afatfsFilePtr_t file); static uint32_t roundUpTo(uint32_t value, uint32_t rounding) { @@ -659,6 +664,7 @@ bool afatfs_flush() if (afatfs.cacheDescriptor[i].state == AFATFS_CACHE_STATE_DIRTY && !afatfs.cacheDescriptor[i].locked) { afatfs_cacheFlushSector(i); + // That flush will take time to complete so we may as well tell caller to come back later return false; } } @@ -957,9 +963,13 @@ static afatfsOperationStatus_e afatfs_FATSetNextCluster(uint32_t startCluster, u static void afatfs_fileUnlockCacheSector(afatfsFilePtr_t file) { - if (file->lockedCacheIndex != -1) { - afatfs.cacheDescriptor[file->lockedCacheIndex].locked = 0; - file->lockedCacheIndex = -1; + if (file->writeLockedCacheIndex != -1) { + afatfs.cacheDescriptor[file->writeLockedCacheIndex].locked = 0; + file->writeLockedCacheIndex = -1; + } + if (file->readRetainCacheIndex != -1) { + afatfs.cacheDescriptor[file->readRetainCacheIndex].retainCount = MAX(afatfs.cacheDescriptor[file->readRetainCacheIndex].retainCount - 1, 0); + file->readRetainCacheIndex = -1; } } @@ -1209,6 +1219,9 @@ static afatfsOperationStatus_e afatfs_saveDirectoryEntry(afatfsFilePtr_t file) uint8_t *sector; afatfsOperationStatus_e result; + if (file->directoryEntryPos.sectorNumberPhysical == 0) + return AFATFS_OPERATION_SUCCESS; // Root directories don't have a directory entry + result = afatfs_cacheSector(file->directoryEntryPos.sectorNumberPhysical, §or, AFATFS_CACHE_READ | AFATFS_CACHE_WRITE); if (result == AFATFS_OPERATION_SUCCESS) { @@ -1548,20 +1561,26 @@ static bool afatfs_isEndOfAllocatedFile(afatfsFilePtr_t file) } /** + * Take a lock on the sector at the current file cursor position. * + * Returns a pointer to the sector buffer if successful, or NULL if at the end of file (check afatfs_isEndOfAllocatedFile()) + * or the sector has not yet been read in from disk. */ -static uint8_t* afatfs_fileGetCursorSectorForRead(afatfsFilePtr_t file) +static uint8_t* afatfs_fileRetainCursorSectorForRead(afatfsFilePtr_t file) { uint8_t *result; uint32_t physicalSector = afatfs_fileGetCursorPhysicalSector(file); - if (file->lockedCacheIndex != -1) { - if (!afatfs_assert(physicalSector == afatfs.cacheDescriptor[file->lockedCacheIndex].sectorIndex)) { + /* If we've already got a locked sector then we can assume that was the same one that's at the cursor (because this + * cache is invalidated when crossing a sector boundary) + */ + if (file->readRetainCacheIndex != -1) { + if (!afatfs_assert(physicalSector == afatfs.cacheDescriptor[file->readRetainCacheIndex].sectorIndex)) { return NULL; } - result = afatfs_cacheSectorGetMemory(file->lockedCacheIndex); + result = afatfs_cacheSectorGetMemory(file->readRetainCacheIndex); } else { if (afatfs_isEndOfAllocatedFile(file)) { return NULL; @@ -1572,7 +1591,7 @@ static uint8_t* afatfs_fileGetCursorSectorForRead(afatfsFilePtr_t file) afatfsOperationStatus_e status = afatfs_cacheSector( physicalSector, &result, - AFATFS_CACHE_READ | AFATFS_CACHE_LOCK + AFATFS_CACHE_READ | AFATFS_CACHE_RETAIN ); if (status != AFATFS_OPERATION_SUCCESS) { @@ -1580,7 +1599,67 @@ static uint8_t* afatfs_fileGetCursorSectorForRead(afatfsFilePtr_t file) return NULL; } - file->lockedCacheIndex = afatfs_getCacheDescriptorIndexForBuffer(result); + file->readRetainCacheIndex = afatfs_getCacheDescriptorIndexForBuffer(result); + } + + return result; +} + +/** + * Lock the sector at the file's cursor position for write, and return a reference to the memory for that sector. + * + * Returns NULL if the cache was too busy, try again later. + */ +static uint8_t* afatfs_fileLockCursorSectorForWrite(afatfsFilePtr_t file) +{ + afatfsOperationStatus_e status; + uint8_t *result; + + + if (file->writeLockedCacheIndex != -1) { + uint32_t physicalSector = afatfs_fileGetCursorPhysicalSector(file); + + if (!afatfs_assert(physicalSector == afatfs.cacheDescriptor[file->writeLockedCacheIndex].sectorIndex)) { + return NULL; + } + + result = afatfs_cacheSectorGetMemory(file->writeLockedCacheIndex); + } else { + // Find / allocate a sector and lock it in the cache so we can rely on it sticking around + + // Are we at the start of an empty file or the end of a non-empty file? If so we need to add a cluster + if (afatfs_isEndOfAllocatedFile(file) && afatfs_appendFreeCluster(file) != AFATFS_OPERATION_SUCCESS) { + // The extension of the file is in progress so please call us again later to try again + return NULL; + } + + uint32_t physicalSector = afatfs_fileGetCursorPhysicalSector(file); + uint8_t cacheFlags = AFATFS_CACHE_WRITE | AFATFS_CACHE_LOCK; + uint32_t cursorOffsetInSector = file->cursorOffset % AFATFS_SECTOR_SIZE; + + /* + * If there is data before the write point, or there could be data after the write-point + * then we need to have the original contents of the sector in the cache for us to merge into + */ + if ( + cursorOffsetInSector > 0 + || (file->cursorOffset & ~(AFATFS_SECTOR_SIZE - 1)) + AFATFS_SECTOR_SIZE < file->directoryEntry.fileSize + ) { + cacheFlags |= AFATFS_CACHE_READ; + } + + status = afatfs_cacheSector( + physicalSector, + &result, + cacheFlags + ); + + if (status != AFATFS_OPERATION_SUCCESS) { + // Not enough cache available to accept this write / sector not ready for read + return NULL; + } + + file->writeLockedCacheIndex = afatfs_getCacheDescriptorIndexForBuffer(result); } return result; @@ -1822,7 +1901,7 @@ afatfsOperationStatus_e afatfs_findNext(afatfsFilePtr_t directory, afatfsFinder_ } } - sector = afatfs_fileGetCursorSectorForRead(directory); + sector = afatfs_fileRetainCursorSectorForRead(directory); if (sector) { finder->entryIndex++; @@ -1843,6 +1922,14 @@ afatfsOperationStatus_e afatfs_findNext(afatfsFilePtr_t directory, afatfsFinder_ } } +/** + * Release resources associated with a find operation. + */ +void afatfs_findLast(afatfsFilePtr_t directory) +{ + afatfs_fileUnlockCacheSector(directory); +} + /** * Initialise the finder so that the first call with the directory to findNext() will return the first file in the * directory. @@ -1888,8 +1975,8 @@ static afatfsOperationStatus_e afatfs_extendSubdirectoryContinue(afatfsFile_t *d memset(sectorBuffer, 0, AFATFS_SECTOR_SIZE); - // If this is the first sector of the directory, create the "." and ".." entries - if (directory->cursorOffset == 0) { + // If this is the first sector of a non-root directory, create the "." and ".." entries + if (afatfs.currentDirectory.directoryEntryPos.sectorNumberPhysical != 0 && directory->cursorOffset == 0) { fatDirectoryEntry_t *dirEntries = (fatDirectoryEntry_t *) sectorBuffer; memset(dirEntries[0].filename, ' ', sizeof(dirEntries[0].filename)); @@ -2005,6 +2092,7 @@ static afatfsOperationStatus_e afatfs_allocateDirectoryEntry(afatfsFilePtr_t dir if (fat_isDirectoryEntryEmpty(*dirEntry) || fat_isDirectoryEntryTerminator(*dirEntry)) { afatfs_cacheSectorMarkDirty((uint8_t*) *dirEntry); + afatfs_findLast(directory); return AFATFS_OPERATION_SUCCESS; } } else { @@ -2193,29 +2281,41 @@ static void afatfs_createFileContinue(afatfsFile_t *file) do { status = afatfs_findNext(&afatfs.currentDirectory, &file->directoryEntryPos, &entry); - if (status == AFATFS_OPERATION_SUCCESS) { - if (entry == NULL || fat_isDirectoryEntryTerminator(entry)) { - if ((file->mode & AFATFS_FILE_MODE_CREATE) != 0) { - // The file didn't already exist, so we can create it. Allocate a new directory entry - afatfs_findFirst(&afatfs.currentDirectory, &file->directoryEntryPos); + switch (status) { + case AFATFS_OPERATION_SUCCESS: + // Is this the last entry in the directory? + if (entry == NULL || fat_isDirectoryEntryTerminator(entry)) { + afatfs_findLast(&afatfs.currentDirectory); - opState->phase = AFATFS_CREATEFILE_PHASE_CREATE_NEW_FILE; - goto doMore; - } else { - // File not found. - opState->phase = AFATFS_CREATEFILE_PHASE_FAILURE; - goto doMore; - } - } else if (strncmp(entry->filename, (char*) file->directoryEntry.filename, FAT_FILENAME_LENGTH) == 0) { - // We found a file with this name! - memcpy(&file->directoryEntry, entry, sizeof(file->directoryEntry)); + if ((file->mode & AFATFS_FILE_MODE_CREATE) != 0) { + // The file didn't already exist, so we can create it. Allocate a new directory entry + afatfs_findFirst(&afatfs.currentDirectory, &file->directoryEntryPos); - opState->phase = AFATFS_CREATEFILE_PHASE_SUCCESS; + opState->phase = AFATFS_CREATEFILE_PHASE_CREATE_NEW_FILE; + goto doMore; + } else { + // File not found. + + opState->phase = AFATFS_CREATEFILE_PHASE_FAILURE; + goto doMore; + } + } else if (strncmp(entry->filename, (char*) file->directoryEntry.filename, FAT_FILENAME_LENGTH) == 0) { + // We found a file with this name! + memcpy(&file->directoryEntry, entry, sizeof(file->directoryEntry)); + + afatfs_findLast(&afatfs.currentDirectory); + + opState->phase = AFATFS_CREATEFILE_PHASE_SUCCESS; + goto doMore; + } // Else this entry doesn't match, fall through and continue the search + break; + case AFATFS_OPERATION_FAILURE: + afatfs_findLast(&afatfs.currentDirectory); + opState->phase = AFATFS_CREATEFILE_PHASE_FAILURE; goto doMore; - } - } else if (status == AFATFS_OPERATION_FAILURE) { - opState->phase = AFATFS_CREATEFILE_PHASE_FAILURE; - goto doMore; + break; + case AFATFS_OPERATION_IN_PROGRESS: + ; } } while (status == AFATFS_OPERATION_SUCCESS); break; @@ -2299,7 +2399,8 @@ static void afatfs_createFileContinue(afatfsFile_t *file) static void afatfs_initFileHandle(afatfsFilePtr_t file) { memset(file, 0, sizeof(*file)); - file->lockedCacheIndex = -1; + file->writeLockedCacheIndex = -1; + file->readRetainCacheIndex = -1; } static void afatfs_funlinkContinue(afatfsFilePtr_t file) @@ -2410,12 +2511,12 @@ static void afatfs_closeFileContinue(afatfsFilePtr_t file) /* * Directories don't update their parent directory entries over time, because their fileSize field in the directory - * never changes (when we add the first clsuter to the directory we save the directory entry at that point and it + * never changes (when we add the first cluster to the directory we save the directory entry at that point and it * doesn't change afterwards). * * So don't bother trying to save their directory entries during fclose(). */ - if (file->type != AFATFS_FILE_TYPE_DIRECTORY) { + if (file->type != AFATFS_FILE_TYPE_DIRECTORY && file->type != AFATFS_FILE_TYPE_FAT16_ROOT_DIRECTORY) { if (afatfs_saveDirectoryEntry(file) != AFATFS_OPERATION_SUCCESS) { return; } @@ -2467,16 +2568,20 @@ bool afatfs_fclose(afatfsFilePtr_t file, afatfsCallback_t callback) * Create a new directory with the given name, or open the directory if it already exists. * * The directory will be passed to the callback, or NULL if the creation failed. + * + * Returns true if the directory creation was begun, or false if there are too many open files. */ -afatfsFilePtr_t afatfs_mkdir(const char *filename, afatfsFileCallback_t callback) +bool afatfs_mkdir(const char *filename, afatfsFileCallback_t callback) { afatfsFilePtr_t file = afatfs_allocateFileHandle(); if (file) { afatfs_createFile(file, filename, FAT_FILE_ATTRIBUTE_DIRECTORY, AFATFS_FILE_MODE_CREATE | AFATFS_FILE_MODE_READ | AFATFS_FILE_MODE_WRITE, callback); + } else if (callback) { + callback(NULL); } - return file; + return file != NULL; } /** @@ -2509,6 +2614,9 @@ bool afatfs_chdir(afatfsFilePtr_t directory) afatfs.currentDirectory.directoryEntry.firstClusterLow = afatfs.rootDirectoryCluster & 0xFFFF; afatfs.currentDirectory.directoryEntry.attrib = FAT_FILE_ATTRIBUTE_DIRECTORY; + // Root directories don't have a directory entry to represent themselves: + afatfs.currentDirectory.directoryEntryPos.sectorNumberPhysical = 0; + afatfs_fseek(&afatfs.currentDirectory, 0, AFATFS_SEEK_SET); return true; @@ -2582,76 +2690,17 @@ bool afatfs_fopen(const char *filename, const char *mode, afatfsFileCallback_t c return file != NULL; } -/** - * Lock the sector at the file's cursor position for write, and return a reference to the memory for that sector. - * - * Returns NULL if the cache was too busy, try again later. - */ -static uint8_t* afatfs_fileLockCursorSectorForWrite(afatfsFilePtr_t file) -{ - afatfsOperationStatus_e status; - uint8_t *result; - - - if (file->lockedCacheIndex != -1) { - /*uint32_t physicalSector = afatfs_fileGetCursorPhysicalSector(file); - - if (!afatfs_assert(physicalSector == afatfs.cacheDescriptor[file->lockedCacheIndex].sectorIndex)) { - return NULL; - }*/ - - result = afatfs_cacheSectorGetMemory(file->lockedCacheIndex); - } else { - // Find / allocate a sector and lock it in the cache so we can rely on it sticking around - if (afatfs_isEndOfAllocatedFile(file)) { - ; - } - // Are we at the start of an empty file or the end of a non-empty file? If so we need to add a cluster - if (afatfs_isEndOfAllocatedFile(file) && afatfs_appendFreeCluster(file) != AFATFS_OPERATION_SUCCESS) { - // The extension of the file is in progress so please call us again later to try again - return NULL; - } - - uint32_t physicalSector = afatfs_fileGetCursorPhysicalSector(file); - uint8_t cacheFlags = AFATFS_CACHE_WRITE | AFATFS_CACHE_LOCK; - uint32_t cursorOffsetInSector = file->cursorOffset % AFATFS_SECTOR_SIZE; - - /* - * If there is data before the write point, or there could be data after the write-point - * then we need to have the original contents of the sector in the cache for us to merge into - */ - if ( - cursorOffsetInSector > 0 - || (file->cursorOffset & ~(AFATFS_SECTOR_SIZE - 1)) + AFATFS_SECTOR_SIZE < file->directoryEntry.fileSize - ) { - cacheFlags |= AFATFS_CACHE_READ; - } - - status = afatfs_cacheSector( - physicalSector, - &result, - cacheFlags - ); - - if (status != AFATFS_OPERATION_SUCCESS) { - // Not enough cache available to accept this write / sector not ready for read - return NULL; - } - - file->lockedCacheIndex = afatfs_getCacheDescriptorIndexForBuffer(result); - } - - return result; -} - void afatfs_fputc(afatfsFilePtr_t file, uint8_t c) { uint32_t cursorOffsetInSector = file->cursorOffset % AFATFS_SECTOR_SIZE; - int cacheIndex = file->lockedCacheIndex; + + int cacheIndex = file->writeLockedCacheIndex; if (cacheIndex != -1 && cursorOffsetInSector != AFATFS_SECTOR_SIZE - 1) { afatfs_cacheSectorGetMemory(cacheIndex)[cursorOffsetInSector] = c; file->cursorOffset++; + + // TODO we could probably just defer this to the next seek, file close, or slow-path fwrite() call? file->directoryEntry.fileSize = MAX(file->directoryEntry.fileSize, file->cursorOffset); } else { // Slow path @@ -2754,7 +2803,7 @@ uint32_t afatfs_fread(afatfsFilePtr_t file, uint8_t *buffer, uint32_t len) uint32_t bytesToReadThisSector = MIN(AFATFS_SECTOR_SIZE - cursorOffsetInSector, len); uint8_t *sectorBuffer; - sectorBuffer = afatfs_fileGetCursorSectorForRead(file); + sectorBuffer = afatfs_fileRetainCursorSectorForRead(file); if (!sectorBuffer) { // Cache is currently busy return readBytes; @@ -3111,27 +3160,45 @@ void afatfs_init() */ bool afatfs_destroy() { - // Don't attempt detailed cleanup if the filesystem is in an odd state + // Only attempt detailed cleanup if the filesystem is in reasonable looking state if (afatfs.filesystemState == AFATFS_FILESYSTEM_STATE_READY) { -#ifdef AFATFS_USE_FREEFILE - afatfs_fclose(&afatfs.freeFile, NULL); -#endif + int openFileCount = 0; for (int i = 0; i < AFATFS_MAX_OPEN_FILES; i++) { - afatfs_fclose(&afatfs.openFiles[i], NULL); - } - - afatfs_poll(); - - for (int i = 0; i < AFATFS_NUM_CACHE_SECTORS; i++) { - // Flush even if the pages are "locked" - if (afatfs.cacheDescriptor[i].state == AFATFS_CACHE_STATE_DIRTY) { - if (afatfs_cacheFlushSector(i)) { - // Card will be busy making that write so don't bother trying to flush any other pages right now - return false; - } + if (afatfs.openFiles[i].type != AFATFS_FILE_TYPE_NONE) { + afatfs_fclose(&afatfs.openFiles[i], NULL); + openFileCount++; } } + +#ifdef AFATFS_USE_FREEFILE + if (afatfs.freeFile.type != AFATFS_FILE_TYPE_NONE) { + afatfs_fclose(&afatfs.freeFile, NULL); + openFileCount++; + } +#endif + if (afatfs.currentDirectory.type != AFATFS_FILE_TYPE_NONE) { + afatfs_fclose(&afatfs.currentDirectory, NULL); + openFileCount++; + } + + if (!afatfs_flush()) { + afatfs_poll(); + return false; + } + + if (openFileCount > 0) { + return false; + } + +#ifdef AFATFS_DEBUG + /* All sector locks should have been released by closing the files, so the subsequent flush should have written + * all dirty pages to disk. If not, something's wrong: + */ + for (int i = 0; i < AFATFS_NUM_CACHE_SECTORS; i++) { + afatfs_assert(afatfs.cacheDescriptor[i].state != AFATFS_CACHE_STATE_DIRTY); + } +#endif } // Clear the afatfs so it's as if we never ran diff --git a/src/main/io/asyncfatfs/asyncfatfs.h b/src/main/io/asyncfatfs/asyncfatfs.h index cad13faf0a..afe94a2bca 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.h +++ b/src/main/io/asyncfatfs/asyncfatfs.h @@ -48,11 +48,12 @@ uint32_t afatfs_fread(afatfsFilePtr_t file, uint8_t *buffer, uint32_t len); afatfsOperationStatus_e afatfs_fseek(afatfsFilePtr_t file, int32_t offset, afatfsSeek_e whence); bool afatfs_ftell(afatfsFilePtr_t file, uint32_t *position); -afatfsFilePtr_t afatfs_mkdir(const char *filename, afatfsFileCallback_t complete); +bool afatfs_mkdir(const char *filename, afatfsFileCallback_t complete); bool afatfs_chdir(afatfsFilePtr_t dirHandle); void afatfs_findFirst(afatfsFilePtr_t directory, afatfsFinder_t *finder); afatfsOperationStatus_e afatfs_findNext(afatfsFilePtr_t directory, afatfsFinder_t *finder, fatDirectoryEntry_t **dirEntry); +void afatfs_findLast(afatfsFilePtr_t directory); bool afatfs_flush(); void afatfs_init(); diff --git a/src/main/io/serial_cli.c b/src/main/io/serial_cli.c index 4a2bd168e6..c87540a506 100644 --- a/src/main/io/serial_cli.c +++ b/src/main/io/serial_cli.c @@ -353,7 +353,7 @@ static const char * const lookupTableGimbalMode[] = { }; static const char * const lookupTableBlackboxDevice[] = { - "SERIAL", "SPIFLASH" + "SERIAL", "SPIFLASH", "SDCARD" }; diff --git a/src/main/main.c b/src/main/main.c index 7799a5b57f..431b501e1b 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -65,6 +65,7 @@ #include "io/gimbal.h" #include "io/ledstrip.h" #include "io/display.h" +#include "io/asyncfatfs/asyncfatfs.h" #include "sensors/sensors.h" #include "sensors/sonar.h" @@ -538,6 +539,7 @@ void init(void) #ifdef USE_SDCARD sdcard_init(); + afatfs_init(); #endif #ifdef BLACKBOX