#include #include #include #include #include #include "platform.h" #ifdef BLACKBOX #include "blackbox.h" #include "blackbox_io.h" #include "build/version.h" #include "build/build_config.h" #include "common/encoding.h" #include "common/maths.h" #include "common/printf.h" #include "config/parameter_group.h" #include "config/parameter_group_ids.h" #include "fc/config.h" #include "fc/rc_controls.h" #include "flight/pid.h" #include "io/asyncfatfs/asyncfatfs.h" #include "io/flashfs.h" #include "io/serial.h" #include "msp/msp_serial.h" #define BLACKBOX_SERIAL_PORT_MODE MODE_TX // How many bytes can we transmit per loop iteration when writing headers? static uint8_t blackboxMaxHeaderBytesPerIteration; // How many bytes can we write *this* iteration without overflowing transmit buffers or overstressing the OpenLog? int32_t blackboxHeaderBudget; STATIC_UNIT_TESTED serialPort_t *blackboxPort = NULL; #ifndef UNIT_TEST static portSharing_e blackboxPortSharing; #endif #ifdef USE_SDCARD 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_CHANGE_INTO_LOG_DIRECTORY, BLACKBOX_SDCARD_READY_TO_CREATE_LOG, BLACKBOX_SDCARD_READY_TO_LOG } state; } blackboxSDCard; #define LOGFILE_PREFIX "LOG" #define LOGFILE_SUFFIX "BFL" #endif #ifndef UNIT_TEST void blackboxOpen() { serialPort_t *sharedBlackboxAndMspPort = findSharedSerialPort(FUNCTION_BLACKBOX, FUNCTION_MSP); if (sharedBlackboxAndMspPort) { mspSerialReleasePortIfAllocated(sharedBlackboxAndMspPort); } } #endif void blackboxWrite(uint8_t value) { switch (blackboxConfig()->device) { #ifdef USE_FLASHFS case BLACKBOX_DEVICE_FLASH: flashfsWriteByte(value); // Write byte asynchronously break; #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: afatfs_fputc(blackboxSDCard.logFile, value); break; #endif case BLACKBOX_DEVICE_SERIAL: default: serialWrite(blackboxPort, value); break; } } static void _putc(void *p, char c) { (void)p; blackboxWrite(c); } static int blackboxPrintfv(const char *fmt, va_list va) { return tfp_format(NULL, _putc, fmt, va); } //printf() to the blackbox serial port with no blocking shenanigans (so it's caller's responsibility to not write too fast!) int blackboxPrintf(const char *fmt, ...) { va_list va; va_start(va, fmt); int written = blackboxPrintfv(fmt, va); va_end(va); return written; } /* * printf a Blackbox header line with a leading "H " and trailing "\n" added automatically. blackboxHeaderBudget is * decreased to account for the number of bytes written. */ void blackboxPrintfHeaderLine(const char *name, const char *fmt, ...) { va_list va; blackboxWrite('H'); blackboxWrite(' '); blackboxPrint(name); blackboxWrite(':'); va_start(va, fmt); int written = blackboxPrintfv(fmt, va); va_end(va); blackboxWrite('\n'); blackboxHeaderBudget -= written + 3; } // Print the null-terminated string 's' to the blackbox device and return the number of bytes written int blackboxPrint(const char *s) { int length; const uint8_t *pos; switch (blackboxConfig()->device) { #ifdef USE_FLASHFS case BLACKBOX_DEVICE_FLASH: length = strlen(s); flashfsWrite((const uint8_t*) s, length, false); // Write asynchronously break; #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: length = strlen(s); afatfs_fwrite(blackboxSDCard.logFile, (const uint8_t*) s, length); // Ignore failures due to buffers filling up break; #endif case BLACKBOX_DEVICE_SERIAL: default: pos = (uint8_t*) s; while (*pos) { serialWrite(blackboxPort, *pos); pos++; } length = pos - (uint8_t*) s; break; } return length; } /** * Write an unsigned integer to the blackbox serial port using variable byte encoding. */ void blackboxWriteUnsignedVB(uint32_t value) { //While this isn't the final byte (we can only write 7 bits at a time) while (value > 127) { blackboxWrite((uint8_t) (value | 0x80)); // Set the high bit to mean "more bytes follow" value >>= 7; } blackboxWrite(value); } /** * Write a signed integer to the blackbox serial port using ZigZig and variable byte encoding. */ void blackboxWriteSignedVB(int32_t value) { //ZigZag encode to make the value always positive blackboxWriteUnsignedVB(zigzagEncode(value)); } void blackboxWriteSignedVBArray(int32_t *array, int count) { for (int i = 0; i < count; i++) { blackboxWriteSignedVB(array[i]); } } void blackboxWriteSigned16VBArray(int16_t *array, int count) { for (int i = 0; i < count; i++) { blackboxWriteSignedVB(array[i]); } } void blackboxWriteS16(int16_t value) { blackboxWrite(value & 0xFF); blackboxWrite((value >> 8) & 0xFF); } /** * Write a 2 bit tag followed by 3 signed fields of 2, 4, 6 or 32 bits */ void blackboxWriteTag2_3S32(int32_t *values) { static const int NUM_FIELDS = 3; //Need to be enums rather than const ints if we want to switch on them (due to being C) enum { BITS_2 = 0, BITS_4 = 1, BITS_6 = 2, BITS_32 = 3 }; enum { BYTES_1 = 0, BYTES_2 = 1, BYTES_3 = 2, BYTES_4 = 3 }; int x; int selector = BITS_2, selector2; /* * Find out how many bits the largest value requires to encode, and use it to choose one of the packing schemes * below: * * Selector possibilities * * 2 bits per field ss11 2233, * 4 bits per field ss00 1111 2222 3333 * 6 bits per field ss11 1111 0022 2222 0033 3333 * 32 bits per field sstt tttt followed by fields of various byte counts */ for (x = 0; x < NUM_FIELDS; x++) { //Require more than 6 bits? if (values[x] >= 32 || values[x] < -32) { selector = BITS_32; break; } //Require more than 4 bits? if (values[x] >= 8 || values[x] < -8) { if (selector < BITS_6) { selector = BITS_6; } } else if (values[x] >= 2 || values[x] < -2) { //Require more than 2 bits? if (selector < BITS_4) { selector = BITS_4; } } } switch (selector) { case BITS_2: blackboxWrite((selector << 6) | ((values[0] & 0x03) << 4) | ((values[1] & 0x03) << 2) | (values[2] & 0x03)); break; case BITS_4: blackboxWrite((selector << 6) | (values[0] & 0x0F)); blackboxWrite((values[1] << 4) | (values[2] & 0x0F)); break; case BITS_6: blackboxWrite((selector << 6) | (values[0] & 0x3F)); blackboxWrite((uint8_t)values[1]); blackboxWrite((uint8_t)values[2]); break; case BITS_32: /* * Do another round to compute a selector for each field, assuming that they are at least 8 bits each * * Selector2 field possibilities * 0 - 8 bits * 1 - 16 bits * 2 - 24 bits * 3 - 32 bits */ selector2 = 0; //Encode in reverse order so the first field is in the low bits: for (x = NUM_FIELDS - 1; x >= 0; x--) { selector2 <<= 2; if (values[x] < 128 && values[x] >= -128) { selector2 |= BYTES_1; } else if (values[x] < 32768 && values[x] >= -32768) { selector2 |= BYTES_2; } else if (values[x] < 8388608 && values[x] >= -8388608) { selector2 |= BYTES_3; } else { selector2 |= BYTES_4; } } //Write the selectors blackboxWrite((selector << 6) | selector2); //And now the values according to the selectors we picked for them for (x = 0; x < NUM_FIELDS; x++, selector2 >>= 2) { switch (selector2 & 0x03) { case BYTES_1: blackboxWrite(values[x]); break; case BYTES_2: blackboxWrite(values[x]); blackboxWrite(values[x] >> 8); break; case BYTES_3: blackboxWrite(values[x]); blackboxWrite(values[x] >> 8); blackboxWrite(values[x] >> 16); break; case BYTES_4: blackboxWrite(values[x]); blackboxWrite(values[x] >> 8); blackboxWrite(values[x] >> 16); blackboxWrite(values[x] >> 24); break; } } break; } } /** * Write an 8-bit selector followed by four signed fields of size 0, 4, 8 or 16 bits. */ void blackboxWriteTag8_4S16(int32_t *values) { //Need to be enums rather than const ints if we want to switch on them (due to being C) enum { FIELD_ZERO = 0, FIELD_4BIT = 1, FIELD_8BIT = 2, FIELD_16BIT = 3 }; uint8_t selector, buffer; int nibbleIndex; int x; selector = 0; //Encode in reverse order so the first field is in the low bits: for (x = 3; x >= 0; x--) { selector <<= 2; if (values[x] == 0) { selector |= FIELD_ZERO; } else if (values[x] < 8 && values[x] >= -8) { selector |= FIELD_4BIT; } else if (values[x] < 128 && values[x] >= -128) { selector |= FIELD_8BIT; } else { selector |= FIELD_16BIT; } } blackboxWrite(selector); nibbleIndex = 0; buffer = 0; for (x = 0; x < 4; x++, selector >>= 2) { switch (selector & 0x03) { case FIELD_ZERO: //No-op break; case FIELD_4BIT: if (nibbleIndex == 0) { //We fill high-bits first buffer = values[x] << 4; nibbleIndex = 1; } else { blackboxWrite(buffer | (values[x] & 0x0F)); nibbleIndex = 0; } break; case FIELD_8BIT: if (nibbleIndex == 0) { blackboxWrite(values[x]); } else { //Write the high bits of the value first (mask to avoid sign extension) blackboxWrite(buffer | ((values[x] >> 4) & 0x0F)); //Now put the leftover low bits into the top of the next buffer entry buffer = values[x] << 4; } break; case FIELD_16BIT: if (nibbleIndex == 0) { //Write high byte first blackboxWrite(values[x] >> 8); blackboxWrite(values[x]); } else { //First write the highest 4 bits blackboxWrite(buffer | ((values[x] >> 12) & 0x0F)); // Then the middle 8 blackboxWrite(values[x] >> 4); //Only the smallest 4 bits are still left to write buffer = values[x] << 4; } break; } } //Anything left over to write? if (nibbleIndex == 1) { blackboxWrite(buffer); } } /** * Write `valueCount` fields from `values` to the Blackbox using signed variable byte encoding. A 1-byte header is * written first which specifies which fields are non-zero (so this encoding is compact when most fields are zero). * * valueCount must be 8 or less. */ void blackboxWriteTag8_8SVB(int32_t *values, int valueCount) { uint8_t header; int i; if (valueCount > 0) { //If we're only writing one field then we can skip the header if (valueCount == 1) { blackboxWriteSignedVB(values[0]); } else { //First write a one-byte header that marks which fields are non-zero header = 0; // First field should be in low bits of header for (i = valueCount - 1; i >= 0; i--) { header <<= 1; if (values[i] != 0) { header |= 0x01; } } blackboxWrite(header); for (i = 0; i < valueCount; i++) { if (values[i] != 0) { blackboxWriteSignedVB(values[i]); } } } } } /** Write unsigned integer **/ void blackboxWriteU32(int32_t value) { blackboxWrite(value & 0xFF); blackboxWrite((value >> 8) & 0xFF); blackboxWrite((value >> 16) & 0xFF); blackboxWrite((value >> 24) & 0xFF); } /** Write float value in the integer form **/ void blackboxWriteFloat(float value) { blackboxWriteU32(castFloatBytesToInt(value)); } /** * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now. * * Intended to be called regularly for the blackbox device to perform housekeeping. */ void blackboxDeviceFlush(void) { switch (blackboxConfig()->device) { #ifdef USE_FLASHFS /* * This is our only output device which requires us to call flush() in order for it to write anything. The other * devices will progressively write in the background without Blackbox calling anything. */ case BLACKBOX_DEVICE_FLASH: flashfsFlushAsync(); break; #endif default: ; } } /** * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now. * * Returns true if all data has been written to the device. */ bool blackboxDeviceFlushForce(void) { switch (blackboxConfig()->device) { case BLACKBOX_DEVICE_SERIAL: // Nothing to speed up flushing on serial, as serial is continuously being drained out of its buffer return isSerialTransmitBufferEmpty(blackboxPort); #ifdef USE_FLASHFS case BLACKBOX_DEVICE_FLASH: return flashfsFlushAsync(); #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: /* SD card will flush itself without us calling it, but we need to call flush manually in order to check * if it's done yet or not! */ return afatfs_flush(); #endif default: return false; } } /** * Attempt to open the logging device. Returns true if successful. */ #ifndef UNIT_TEST bool blackboxDeviceOpen(void) { switch (blackboxConfig()->device) { case BLACKBOX_DEVICE_SERIAL: { serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_BLACKBOX); baudRate_e baudRateIndex; portOptions_t portOptions = SERIAL_PARITY_NO | SERIAL_NOT_INVERTED; if (!portConfig) { return false; } blackboxPortSharing = determinePortSharing(portConfig, FUNCTION_BLACKBOX); baudRateIndex = portConfig->blackbox_baudrateIndex; if (baudRates[baudRateIndex] == 230400) { /* * OpenLog's 230400 baud rate is very inaccurate, so it requires a larger inter-character gap in * order to maintain synchronization. */ portOptions |= SERIAL_STOPBITS_2; } else { portOptions |= SERIAL_STOPBITS_1; } blackboxPort = openSerialPort(portConfig->identifier, FUNCTION_BLACKBOX, NULL, baudRates[baudRateIndex], BLACKBOX_SERIAL_PORT_MODE, portOptions); /* * The slowest MicroSD cards have a write latency approaching 150ms. The OpenLog's buffer is about 900 * bytes. In order for its buffer to be able to absorb this latency we must write slower than 6000 B/s. * * So: * Bytes per loop iteration = floor((looptime_ns / 1000000.0) * 6000) * = floor((looptime_ns * 6000) / 1000000.0) * = floor((looptime_ns * 3) / 500.0) * = (looptime_ns * 3) / 500 */ blackboxMaxHeaderBytesPerIteration = constrain((targetPidLooptime * 3) / 500, 1, BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION); return blackboxPort != NULL; } break; #ifdef USE_FLASHFS case BLACKBOX_DEVICE_FLASH: if (flashfsGetSize() == 0 || isBlackboxDeviceFull()) { return false; } blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION; return true; break; #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL || afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN || afatfs_isFull()) { return false; } blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION; return true; break; #endif default: return false; } } #endif /** * Erase all blackbox logs */ #ifdef USE_FLASHFS void blackboxEraseAll(void) { switch (blackboxConfig()->device) { case BLACKBOX_DEVICE_FLASH: flashfsEraseCompletely(); break; default: //not supported break; } } /** * Check to see if erasing is done */ bool isBlackboxErased(void) { switch (blackboxConfig()->device) { case BLACKBOX_DEVICE_FLASH: return flashfsIsReady(); break; default: //not supported return true; break; } } #endif /** * Close the Blackbox logging device immediately without attempting to flush any remaining data. */ #ifndef UNIT_TEST void blackboxDeviceClose(void) { switch (blackboxConfig()->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; /* * Normally this would be handled by mw.c, but since we take an unknown amount * of time to shut down asynchronously, we're the only ones that know when to call it. */ if (blackboxPortSharing == PORTSHARING_SHARED) { mspSerialAllocatePorts(); } break; default: ; } } #endif #ifdef USE_SDCARD static void blackboxLogDirCreated(afatfsFilePtr_t directory) { if (directory) { blackboxSDCard.logDirectory = directory; afatfs_findFirst(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder); blackboxSDCard.state = BLACKBOX_SDCARD_ENUMERATE_FILES; } else { // Retry blackboxSDCard.state = BLACKBOX_SDCARD_INITIAL; } } static void blackboxLogFileCreated(afatfsFilePtr_t file) { if (file) { blackboxSDCard.logFile = file; blackboxSDCard.largestLogFileNumber++; blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_LOG; } else { // Retry blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; } } static void blackboxCreateLogFile() { uint32_t remainder = blackboxSDCard.largestLogFileNumber + 1; char filename[] = LOGFILE_PREFIX "00000." LOGFILE_SUFFIX; for (int i = 7; i >= 3; i--) { filename[i] = (remainder % 10) + '0'; remainder /= 10; } blackboxSDCard.state = BLACKBOX_SDCARD_WAITING; afatfs_fopen(filename, "as", blackboxLogFileCreated); } /** * Begin a new log on the SDCard. * * Keep calling until the function returns true (open is complete). */ 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); } break; case BLACKBOX_SDCARD_WAITING: // Waiting for directory entry to be created break; 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 (strncmp(directoryEntry->filename, LOGFILE_PREFIX, strlen(LOGFILE_PREFIX)) == 0 && strncmp(directoryEntry->filename + 8, LOGFILE_SUFFIX, strlen(LOGFILE_SUFFIX)) == 0) { char logSequenceNumberString[6]; memcpy(logSequenceNumberString, directoryEntry->filename + 3, 5); logSequenceNumberString[5] = '\0'; blackboxSDCard.largestLogFileNumber = MAX((uint32_t) atoi(logSequenceNumberString), blackboxSDCard.largestLogFileNumber); } } else { // We're done checking all the files on the card, now we can create a new log file afatfs_findLast(blackboxSDCard.logDirectory); blackboxSDCard.state = BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY; goto doMore; } } break; case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY: // Change into the log directory: if (afatfs_chdir(blackboxSDCard.logDirectory)) { // We no longer need our open handle on the log directory afatfs_fclose(blackboxSDCard.logDirectory, NULL); blackboxSDCard.logDirectory = NULL; blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG; goto doMore; } break; case BLACKBOX_SDCARD_READY_TO_CREATE_LOG: blackboxCreateLogFile(); break; case BLACKBOX_SDCARD_READY_TO_LOG: return true; // Log has been created! } // Not finished init yet return false; } #endif /** * 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 (blackboxConfig()->device) { #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: return blackboxSDCardBeginLog(); #endif default: return true; } } /** * Terminate the current log (for devices which support separations between the logs of multiple flights). * * retainLog - Pass true if the log should be kept, or false if the log should be discarded (if supported). * * Keep calling until this returns true */ bool blackboxDeviceEndLog(bool retainLog) { #ifndef USE_SDCARD (void) retainLog; #endif switch (blackboxConfig()->device) { #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: // Keep retrying until the close operation queues if ( (retainLog && afatfs_fclose(blackboxSDCard.logFile, NULL)) || (!retainLog && afatfs_funlink(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; } } bool isBlackboxDeviceFull(void) { switch (blackboxConfig()->device) { case BLACKBOX_DEVICE_SERIAL: return false; #ifdef USE_FLASHFS case BLACKBOX_DEVICE_FLASH: return flashfsIsEOF(); #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: return afatfs_isFull(); #endif default: return false; } } /** * Call once every loop iteration in order to maintain the global blackboxHeaderBudget with the number of bytes we can * transmit this iteration. */ void blackboxReplenishHeaderBudget() { int32_t freeSpace; switch (blackboxConfig()->device) { case BLACKBOX_DEVICE_SERIAL: freeSpace = serialTxBytesFree(blackboxPort); break; #ifdef USE_FLASHFS case BLACKBOX_DEVICE_FLASH: freeSpace = flashfsGetWriteBufferFreeSpace(); break; #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: freeSpace = afatfs_getFreeBufferSpace(); break; #endif default: freeSpace = 0; } blackboxHeaderBudget = MIN(MIN(freeSpace, blackboxHeaderBudget + blackboxMaxHeaderBytesPerIteration), BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET); } /** * You must call this function before attempting to write Blackbox header bytes to ensure that the write will not * cause buffers to overflow. The number of bytes you can write is capped by the blackboxHeaderBudget. Calling this * reservation function doesn't decrease blackboxHeaderBudget, so you must manually decrement that variable by the * number of bytes you actually wrote. * * When the Blackbox device is FlashFS, a successful return code guarantees that no data will be lost if you write that * many bytes to the device (i.e. FlashFS's buffers won't overflow). * * When the device is a serial port, a successful return code guarantees that Cleanflight's serial Tx buffer will not * overflow, and the outgoing bandwidth is likely to be small enough to give the OpenLog time to absorb MicroSD card * latency. However the OpenLog could still end up silently dropping data. * * Returns: * BLACKBOX_RESERVE_SUCCESS - Upon success * BLACKBOX_RESERVE_TEMPORARY_FAILURE - The buffer is currently too full to service the request, try again later * BLACKBOX_RESERVE_PERMANENT_FAILURE - The buffer is too small to ever service this request */ blackboxBufferReserveStatus_e blackboxDeviceReserveBufferSpace(int32_t bytes) { if (bytes <= blackboxHeaderBudget) { return BLACKBOX_RESERVE_SUCCESS; } // Handle failure: switch (blackboxConfig()->device) { case BLACKBOX_DEVICE_SERIAL: /* * One byte of the tx buffer isn't available for user data (due to its circular list implementation), * hence the -1. Note that the USB VCP implementation doesn't use a buffer and has txBufferSize set to zero. */ if (blackboxPort->txBufferSize && bytes > (int32_t) blackboxPort->txBufferSize - 1) { return BLACKBOX_RESERVE_PERMANENT_FAILURE; } return BLACKBOX_RESERVE_TEMPORARY_FAILURE; #ifdef USE_FLASHFS case BLACKBOX_DEVICE_FLASH: if (bytes > (int32_t) flashfsGetWriteBufferSize()) { return BLACKBOX_RESERVE_PERMANENT_FAILURE; } if (bytes > (int32_t) flashfsGetWriteBufferFreeSpace()) { /* * The write doesn't currently fit in the buffer, so try to make room for it. Our flushing here means * that the Blackbox header writing code doesn't have to guess about the best time to ask flashfs to * flush, and doesn't stall waiting for a flush that would otherwise not automatically be called. */ flashfsFlushAsync(); } return BLACKBOX_RESERVE_TEMPORARY_FAILURE; #endif #ifdef USE_SDCARD case BLACKBOX_DEVICE_SDCARD: // Assume that all writes will fit in the SDCard's buffers return BLACKBOX_RESERVE_TEMPORARY_FAILURE; #endif default: return BLACKBOX_RESERVE_PERMANENT_FAILURE; } } #endif