/** * @file mmc_card.cpp * * @date Dec 28, 2013 * @author Kot_dnz * @author Andrey Belomutskiy, (c) 2012-2020 * * default pinouts in case of SPI2 connected to MMC: PB13 - SCK, PB14 - MISO, PB15 - MOSI, PD4 - CS, 3.3v * default pinouts in case of SPI3 connected to MMC: PB3 - SCK, PB4 - MISO, PB5 - MOSI, PD4 - CS, 3.3v * * * todo: extract some logic into a controller file */ #include "pch.h" #if EFI_FILE_LOGGING #include "buffered_writer.h" #include "status_loop.h" #include "binary_logging.h" static bool fs_ready = false; #if EFI_PROD_CODE int totalLoggedBytes = 0; static int writeCounter = 0; static int totalWritesCounter = 0; static int totalSyncCounter = 0; #include #include #include "mmc_card.h" #include "ff.h" #include "mmc_card_util.h" #include "mass_storage_init.h" #include "hellen_meta.h" #include "rtc_helper.h" // This is dirty workaround to fix compilation without adding this function prototype // to error_handling.h file that will also need to add "ff.h" include to same file and // cause simulator fail to build. extern void errorHandlerWriteReportFile(FIL *fd, int index); #define SD_STATE_INIT "init" #define SD_STATE_MOUNTED "MOUNTED" #define SD_STATE_MOUNT_FAILED "MOUNT_FAILED" #define SD_STATE_OPEN_FAILED "OPEN_FAILED" #define SD_STATE_SEEK_FAILED "SEEK_FAILED" #define SD_STATE_NOT_INSERTED "NOT_INSERTED" #define SD_STATE_CONNECTING "CONNECTING" #define SD_STATE_MSD "MSD" #define SD_STATE_NOT_CONNECTED "NOT_CONNECTED" #define SD_STATE_MMC_FAILED "MMC_CONNECT_FAILED" // todo: shall we migrate to enum with enum2string for consistency? maybe not until we start reading sdStatus? static const char *sdStatus = SD_STATE_INIT; // at about 20Hz we write about 2Kb per second, looks like we flush once every ~2 seconds #define F_SYNC_FREQUENCY 10 /** * on't re-read SD card spi device after boot - it could change mid transaction (TS thread could preempt), * which will cause disaster (usually multiple-unlock of the same mutex in UNLOCK_SD_SPI) */ static spi_device_e mmcSpiDevice = SPI_NONE; #define RUSEFI_LOG_PREFIX "re_" #define PREFIX_LEN 3 #define SHORT_TIME_LEN 13 #define FILE_LIST_MAX_COUNT 20 #if HAL_USE_MMC_SPI /** * MMC driver instance. */ MMCDriver MMCD1; /* MMC/SD over SPI driver configuration.*/ static MMCConfig mmccfg = { .spip = NULL, .lscfg = &mmc_ls_spicfg, .hscfg = &mmc_hs_spicfg }; #if MMC_USE_MUTUAL_EXCLUSION == TRUE #define LOCK_SD_SPI() #define UNLOCK_SD_SPI() #else #define LOCK_SD_SPI() lockSpi(mmcSpiDevice) #define UNLOCK_SD_SPI() unlockSpi(mmcSpiDevice) #endif #endif /* HAL_USE_MMC_SPI */ /** * fatfs MMC/SPI */ static NO_CACHE FATFS MMC_FS; static int fatFsErrors = 0; static void setSdCardReady(bool value) { fs_ready = value; } /* See ff.h FRESULT enum */ static const char *fatErrors[] = { "FR_OK: Succeeded", "FR_DISK_ERR: A hard error occurred in the low level disk I/O layer", "FR_INT_ERR: Assertion failed", "FR_NOT_READY: The physical drive cannot work", "FR_NO_FILE: Could not find the file", "FR_NO_PATH: Could not find the path", "FR_INVALID_NAME: The path name format is invalid", "FR_DENIED: Access denied due to prohibited access or directory full", "FR_EXIST: Access denied due to prohibited access", "FR_INVALID_OBJECT: The file/directory object is invalid", "FR_WRITE_PROTECTED: The physical drive is write protected", "FR_INVALID_DRIVE: The logical drive number is invalid", "FR_NOT_ENABLED: The volume has no work area", "FR_NO_FILESYSTEM: There is no valid FAT volume", "FR_MKFS_ABORTED: The f_mkfs() aborted due to any problem", "FR_TIMEOUT: Could not get a grant to access the volume within defined period", "FR_LOCKED: The operation is rejected according to the file sharing policy", "FR_NOT_ENOUGH_CORE: LFN working buffer could not be allocated", "FR_TOO_MANY_OPEN_FILES: Number of open files > FF_FS_LOCK", "FR_INVALID_PARAMETER: Given parameter is invalid" }; // print FAT error function void printError(const char *str, FRESULT f_error) { if (fatFsErrors++ > 16) { // no reason to spam the console return; } efiPrintf("FATfs Error \"%s\" %d %s", str, f_error, f_error <= FR_INVALID_PARAMETER ? fatErrors[f_error] : "unknown"); } // Warning: shared between all FS users, please release it after use static FIL FDLogFile NO_CACHE; extern int logFileIndex; static char logName[_MAX_FILLER + 20]; static void printMmcPinout() { efiPrintf("MMC CS %s", hwPortname(engineConfiguration->sdCardCsPin)); // todo: we need to figure out the right SPI pinout, not just SPI2 // efiPrintf("MMC SCK %s:%d", portname(EFI_SPI2_SCK_PORT), EFI_SPI2_SCK_PIN); // efiPrintf("MMC MISO %s:%d", portname(EFI_SPI2_MISO_PORT), EFI_SPI2_MISO_PIN); // efiPrintf("MMC MOSI %s:%d", portname(EFI_SPI2_MOSI_PORT), EFI_SPI2_MOSI_PIN); } static void sdStatistics() { printMmcPinout(); efiPrintf("SD enabled=%s status=%s", boolToString(engineConfiguration->isSdCardEnabled), sdStatus); printSpiConfig("SD", mmcSpiDevice); #if HAL_USE_MMC_SPI && (defined(STM32F4XX) || defined(STM32F7XX)) efiPrintf("HS clock %d Hz", spiGetBaseClock(mmccfg.spip) / (2 << ((mmc_hs_spicfg.cr1 & SPI_CR1_BR_Msk) >> SPI_CR1_BR_Pos))); efiPrintf("LS clock %d Hz", spiGetBaseClock(mmccfg.spip) / (2 << ((mmc_ls_spicfg.cr1 & SPI_CR1_BR_Msk) >> SPI_CR1_BR_Pos))); #endif if (isSdCardAlive()) { efiPrintf("filename=%s size=%d", logName, totalLoggedBytes); } #if EFI_FILE_LOGGING efiPrintf("%d SD card fields", getSdCardFieldsCount()); #endif } static void prepareLogFileName() { strcpy(logName, RUSEFI_LOG_PREFIX); char *ptr; // TS SD protocol supports only short 8 symbol file names, good thing that we do not use TS SD protocol! bool result = dateToStringShort(&logName[PREFIX_LEN]); if (result) { ptr = &logName[PREFIX_LEN + SHORT_TIME_LEN]; } else { ptr = itoa10(&logName[PREFIX_LEN], logFileIndex); } if (engineConfiguration->sdTriggerLog) { strcat(ptr, ".teeth"); } else { strcat(ptr, DOT_MLG); } } /** * @brief Create a new file with the specified name * * This function saves the name of the file in a global variable * so that we can later append to that file */ static void createLogFile() { memset(&FDLogFile, 0, sizeof(FIL)); // clear the memory prepareLogFileName(); efiPrintf("starting log file %s", logName); // Create new file. If file is exist - truncate and overwrite, we need header to be at zero offset. FRESULT err = f_open(&FDLogFile, logName, FA_CREATE_ALWAYS | FA_WRITE); if (err != FR_OK && err != FR_EXIST) { sdStatus = SD_STATE_OPEN_FAILED; warning(ObdCode::CUSTOM_ERR_SD_MOUNT_FAILED, "SD: mount failed"); printError("log file create", err); // else - show error return; } setSdCardReady(true); // everything Ok } static void removeFile(const char *pathx) { if (!isSdCardAlive()) { efiPrintf("Error: No File system is mounted"); return; } f_unlink(pathx); } /* * MMC card un-mount. * @return true if we had SD card alive */ bool mmcUnMount() { if (!isSdCardAlive()) { efiPrintf("Error: No File system is mounted. \"mountsd\" first"); return false; } f_close(&FDLogFile); // close file f_sync(&FDLogFile); // sync ALL #if HAL_USE_MMC_SPI blkDisconnect(&MMCD1); // Brings the driver in a state safe for card removal. mmcStop(&MMCD1); // Disables the MMC peripheral. UNLOCK_SD_SPI(); #endif #ifdef EFI_SDC_DEVICE blkDisconnect(&EFI_SDC_DEVICE); sdcStop(&EFI_SDC_DEVICE); #endif f_mount(NULL, 0, 0); // FATFS: Unregister work area prior to discard it memset(&FDLogFile, 0, sizeof(FIL)); // clear FDLogFile setSdCardReady(false); // status = false efiPrintf("MMC/SD card removed"); return true; } #if HAL_USE_USB_MSD static chibios_rt::BinarySemaphore usbConnectedSemaphore(/* taken =*/ true); void onUsbConnectedNotifyMmcI() { usbConnectedSemaphore.signalI(); } #endif /* HAL_USE_USB_MSD */ #if HAL_USE_MMC_SPI /* * Attempts to initialize the MMC card connected over SPI. * Returns a BaseBlockDevice* corresponding to the SD card if successful, otherwise nullptr. */ static BaseBlockDevice* initializeMmcBlockDevice() { // Configures and activates the MMC peripheral. mmcSpiDevice = engineConfiguration->sdCardSpiDevice; // todo: reuse initSpiCs method? mmc_hs_spicfg.ssport = mmc_ls_spicfg.ssport = getHwPort("mmc", engineConfiguration->sdCardCsPin); mmc_hs_spicfg.sspad = mmc_ls_spicfg.sspad = getHwPin("mmc", engineConfiguration->sdCardCsPin); mmccfg.spip = getSpiDevice(mmcSpiDevice); // Invalid SPI device, abort. if (!mmccfg.spip) { return nullptr; } // max SPI rate is 25 MHz after init spiCalcClockDiv(mmccfg.spip, &mmc_hs_spicfg, 25 * 1000 * 1000); // and 250 KHz during initialization spiCalcClockDiv(mmccfg.spip, &mmc_ls_spicfg, 250 * 1000); // We think we have everything for the card, let's try to mount it! mmcObjectInit(&MMCD1); mmcStart(&MMCD1, &mmccfg); // Performs the initialization procedure on the inserted card. LOCK_SD_SPI(); sdStatus = SD_STATE_CONNECTING; if (blkConnect(&MMCD1) != HAL_SUCCESS) { sdStatus = SD_STATE_MMC_FAILED; UNLOCK_SD_SPI(); return nullptr; } // We intentionally never unlock in case of success, we take exclusive access of that spi device for SD use return reinterpret_cast(&MMCD1); } #endif /* HAL_USE_MMC_SPI */ #ifndef RE_SDC_MODE #define RE_SDC_MODE SDC_MODE_4BIT #endif // RE_SDC_MODE // Some ECUs are wired for SDIO/SDMMC instead of SPI #ifdef EFI_SDC_DEVICE static const SDCConfig sdcConfig = { .bus_width = RE_SDC_MODE }; /* * Attempts to initialize the MMC card connected over SDIO. * Returns a BaseBlockDevice* corresponding to the SD card if successful, otherwise nullptr. */ static BaseBlockDevice* initializeMmcBlockDevice() { sdcStart(&EFI_SDC_DEVICE, &sdcConfig); sdStatus = SD_STATE_CONNECTING; if (blkConnect(&EFI_SDC_DEVICE) != HAL_SUCCESS) { sdStatus = SD_STATE_NOT_CONNECTED; return nullptr; } return reinterpret_cast(&EFI_SDC_DEVICE); } #endif /* EFI_SDC_DEVICE */ #if HAL_USE_USB_MSD static bool useMsdMode() { if (errorHandlerIsStartFromError()) { return false; } if (engineConfiguration->alwaysWriteSdCard) { return false; } // Wait for the USB stack to wake up, or a 15 second timeout, whichever occurs first msg_t usbResult = usbConnectedSemaphore.wait(TIME_MS2I(15000)); return usbResult == MSG_OK; } #endif // HAL_USE_USB_MSD static BaseBlockDevice* cardBlockDevice = nullptr; // Initialize SD card. static bool initMmc() { // Don't try to mount SD card in case of fatal error - hardware may be in an unexpected state if (hasFirmwareError()) { return false; } cardBlockDevice = initializeMmcBlockDevice(); #if EFI_TUNER_STUDIO // If not null, card is present engine->outputChannels.sd_present = cardBlockDevice != nullptr; #endif return (cardBlockDevice != nullptr); } // Mount the SD card. // Returns true if the filesystem was successfully mounted for writing. static bool mountMmc() { // if no card, don't try to mount FS if (!cardBlockDevice) { return false; } // We were able to connect the SD card, mount the filesystem memset(&MMC_FS, 0, sizeof(FATFS)); if (f_mount(&MMC_FS, "/", 1) != FR_OK) { sdStatus = SD_STATE_MOUNT_FAILED; return false; } efiPrintf("MMC/SD mounted!"); sdStatus = SD_STATE_MOUNTED; #if EFI_TUNER_STUDIO engine->outputChannels.sd_logging_internal = true; #endif return true; } struct SdLogBufferWriter final : public BufferedWriter<512> { bool failed = false; size_t writeInternal(const char* buffer, size_t count) override { size_t bytesWritten; totalLoggedBytes += count; FRESULT err = f_write(&FDLogFile, buffer, count, &bytesWritten); if (bytesWritten != count) { printError("log file write", err); // Close file and unmount volume mmcUnMount(); failed = true; return 0; } else { writeCounter++; totalWritesCounter++; if (writeCounter >= F_SYNC_FREQUENCY) { /** * Performance optimization: not f_sync after each line, f_sync is probably a heavy operation * todo: one day someone should actually measure the relative cost of f_sync */ f_sync(&FDLogFile); totalSyncCounter++; writeCounter = 0; } } return bytesWritten; } }; #else // not EFI_PROD_CODE (simulator) #include bool initMmc() { // Stub so the loop thinks the MMC is present return true; } bool mountMmc() { // Stub so the loop thinks the MMC mounted OK return true; } class SdLogBufferWriter final : public BufferedWriter<512> { public: bool failed = false; SdLogBufferWriter() : m_stream("rusefi_simulator_log.mlg", std::ios::binary | std::ios::trunc) { fs_ready = true; } size_t writeInternal(const char* buffer, size_t count) override { m_stream.write(buffer, count); m_stream.flush(); return count; } private: std::ofstream m_stream; }; #endif // EFI_PROD_CODE static NO_CACHE SdLogBufferWriter logBuffer; // Log 'regular' ECU log to MLG file static void mlgLogger(); // Log binary trigger log static void sdTriggerLogger(); static THD_WORKING_AREA(mmcThreadStack, 3 * UTILITY_THREAD_STACK_SIZE); // MMC monitor thread static THD_FUNCTION(MMCmonThread, arg) { (void)arg; bool hasUsb = false; chRegSetThreadName("MMC Card Logger"); #if HW_HELLEN && EFI_PROD_CODE // on mega-module we manage SD card power supply while (!getHellenBoardEnabled()) { // wait until board enables peripheral chThdSleepMilliseconds(100); if (getTimeNowS() > 4 && !isIgnVoltage()) { // looks like vehicle is OFF and we are hooked to USB - turn on peripheral to get Mass Storage Device USB profile efiPrintf(" *** turning board ON to power SD card ***"); hellenEnableEn(); break; } } chThdSleepMilliseconds(300); #endif if (!initMmc()) { efiPrintf("Card is not preset/failed to init"); // give up until next boot goto die; } #if HAL_USE_USB_MSD // Wait for the USB stack to wake up, or a 15 second timeout, whichever occurs first // If we have a device AND USB is connected, mount the card to USB, otherwise // mount the null device and try to mount the filesystem ourselves if (useMsdMode()) { hasUsb = true; // Mount the real card to USB attachMsdSdCard(cardBlockDevice); sdStatus = SD_STATE_MSD; // At this point we're done: don't try to write files ourselves } #endif if (!hasUsb) { if (mountMmc()) { incLogFileName(); // TODO: Do this independently of SD mode, somewhere in the start of this task! errorHandlerWriteReportFile(&FDLogFile, logFileIndex); f_setlabel(SD_CARD_LABEL); createLogFile(); if (engineConfiguration->sdTriggerLog) { sdTriggerLogger(); } else { mlgLogger(); } } else { efiPrintf("failed to mount SD card for logging"); } } die: efiPrintf("SD logger has died!"); // exiting thread will create zombie! while(1) { chThdSleepMilliseconds(100); } } void mlgLogger() { while (true) { // if the SPI device got un-picked somehow, cancel SD card // Don't do this check at all if using SDMMC interface instead of SPI #if EFI_PROD_CODE && !defined(EFI_SDC_DEVICE) if (engineConfiguration->sdCardSpiDevice == SPI_NONE) { return; } #endif systime_t before = chVTGetSystemTime(); writeSdLogLine(logBuffer); // Something went wrong (already handled), so cancel further writes if (logBuffer.failed) { return; } auto freq = engineConfiguration->sdCardLogFrequency; if (freq > 250) { freq = 250; } else if (freq < 1) { freq = 1; } systime_t period = CH_CFG_ST_FREQUENCY / freq; chThdSleepUntilWindowed(before, before + period); } } static void sdTriggerLogger() { #if EFI_TOOTH_LOGGER EnableToothLogger(); while (true) { auto buffer = GetToothLoggerBufferBlocking(); // can return nullptr if (buffer) { logBuffer.write(reinterpret_cast(buffer->buffer), buffer->nextIdx * sizeof(composite_logger_s)); ReturnToothLoggerBuffer(buffer); } } #endif /* EFI_TOOTH_LOGGER */ } bool isSdCardAlive(void) { return fs_ready; } void updateSdCardLiveFlags() { #if HAL_USE_MMC_SPI engine->outputChannels.sd_active_wr = (MMCD1.state == BLK_WRITING); engine->outputChannels.sd_active_rd = (MMCD1.state == BLK_READING); #endif #ifdef EFI_SDC_DEVICE engine->outputChannels.sd_active_wr = (EFI_SDC_DEVICE.state == BLK_WRITING); engine->outputChannels.sd_active_rd = (EFI_SDC_DEVICE.state == BLK_READING); #endif } // Pre-config load init void initEarlyMmcCard() { #if EFI_PROD_CODE logName[0] = 0; addConsoleAction("sdinfo", sdStatistics); addConsoleActionS("del", removeFile); //incLogFileName() use same shared FDLogFile, calling it while FDLogFile is used by log writer will cause damage //addConsoleAction("incfilename", incLogFileName); #endif // EFI_PROD_CODE } static bool isSdCardEnabled() { return engineConfiguration->isSdCardEnabled && engineConfiguration->sdCardSpiDevice != SPI_NONE && isBrainPinValid(engineConfiguration->sdCardCsPin); } void initMmcCard() { if (!isSdCardEnabled()) { // do not even bother starting the thread if SD card is not enabled & configured on start-up return; } chThdCreateStatic(mmcThreadStack, sizeof(mmcThreadStack), PRIO_MMC, (tfunc_t)(void*) MMCmonThread, NULL); } #endif /* EFI_FILE_LOGGING */