Files
ardupilot/libraries/AP_HAL_ChibiOS/CrashDump_SD.cpp
T
Andrew Tridgell 280ddc960a AP_HAL_ChibiOS: fix F4 SD crash dump build
Some STM32F4 CMSIS headers omit the unsupported SDIO start-bit error flag. Define it as zero when absent, matching the ChibiOS SDIO driver and allowing DrotekP3Pro builds to compile.
2026-09-04 05:49:07 +10:00

1592 lines
49 KiB
C++

/*
* This file is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <AP_HAL/AP_HAL.h>
#include <AP_Filesystem/AP_Filesystem.h>
#include "CrashDump.h"
#if AP_CRASHDUMP_FATFS_ENABLED && (HAL_USE_SDC || \
(HAL_USE_MMC_SPI && CRASHDUMP_SD_SPI_SUPPORTED_MCU))
#include <AP_Common/AP_FWVersion.h>
#include <AP_Math/crc.h>
#include <hal.h>
#include "hwdef/common/bouncebuffer.h"
#include "sdcard.h"
#include "SPIDevice.h"
#include "hwdef/common/stm32_util.h"
#include "hwdef/common/watchdog.h"
#include <ff.h>
#include <hal_mmcsd.h>
#if HAL_USE_SDC
#include <hal_sdc.h>
#endif
#include <stdio.h>
#include <string.h>
#if HAL_USE_SDC && (defined(STM32H7) || defined(STM32L4PLUS))
#define CRASHDUMP_SD_SPI 0
#define CRASHDUMP_SD_SDMMCV2 1
#define CRASHDUMP_SD_SDMMCV1 0
#define CRASHDUMP_SD_SDIOV1 0
#elif HAL_USE_SDC && (defined(STM32F7) || defined(STM32L4))
#define CRASHDUMP_SD_SPI 0
#define CRASHDUMP_SD_SDMMCV2 0
#define CRASHDUMP_SD_SDMMCV1 1
#define CRASHDUMP_SD_SDIOV1 0
#elif HAL_USE_SDC && defined(STM32F4)
#define CRASHDUMP_SD_SPI 0
#define CRASHDUMP_SD_SDMMCV2 0
#define CRASHDUMP_SD_SDMMCV1 0
#define CRASHDUMP_SD_SDIOV1 1
#elif HAL_USE_MMC_SPI && CRASHDUMP_SD_SPI_SUPPORTED_MCU
#define CRASHDUMP_SD_SPI 1
#define CRASHDUMP_SD_SDMMCV2 0
#define CRASHDUMP_SD_SDMMCV1 0
#define CRASHDUMP_SD_SDIOV1 0
#else
#error "SD crash dumps are not supported on this STM32 family"
#endif
/*
SD crash dumps cannot use FatFs or ChibiOS synchronization from a fault
handler. At boot a file is allocated and its physical sector extents are
recorded. The fault handler then drives SDMMC directly in polling mode.
*/
#define CRASHDUMP_SD_MAX_EXTENTS 64U
#define CRASHDUMP_OVERHEAD (8U * 1024U)
#if CRASHDUMP_SD_SDMMCV2
#define SDMMC_ICR_ALL_FLAGS 0xFFFFFFFFU
#define SDMMC_DATA_ERROR_FLAGS (SDMMC_STA_DCRCFAIL | SDMMC_STA_DTIMEOUT | \
SDMMC_STA_TXUNDERR | SDMMC_STA_RXOVERR)
#elif CRASHDUMP_SD_SDMMCV1
// Some STM32F7 CMSIS headers omit the start-bit error definition.
#ifndef SDMMC_STA_STBITERR
#define SDMMC_STA_STBITERR (0x1UL << 9U)
#endif
#define SDMMC_ICR_ALL_FLAGS 0xFFFFFFFFU
#define SDMMC_DATA_ERROR_FLAGS (SDMMC_STA_DCRCFAIL | SDMMC_STA_DTIMEOUT | \
SDMMC_STA_TXUNDERR | SDMMC_STA_RXOVERR | \
SDMMC_STA_STBITERR)
#elif CRASHDUMP_SD_SDIOV1
// Some STM32F4 CMSIS headers omit the unsupported start-bit error definition.
#ifndef SDIO_STA_STBITERR
#define SDIO_STA_STBITERR 0U
#endif
#define SDIO_DATA_ERROR_FLAGS (SDIO_STA_DCRCFAIL | SDIO_STA_DTIMEOUT | \
SDIO_STA_TXUNDERR | SDIO_STA_RXOVERR | \
SDIO_STA_STBITERR)
#define CRASHDUMP_SDIO_ICR_ALL_FLAGS 0xFFFFFFFFU
#endif
#define CRASHDUMP_TRAILER_VERSION 1U
extern const AP_HAL::HAL& hal;
static constexpr uint8_t crashdump_trailer_magic[8] = {
'A', 'P', 'C', 'D', 'U', 'M', 'P', 0
};
static constexpr char crashdump_reserved_path[] = "APM/CD_Reserved.DAT";
static constexpr char crashdump_published_path[] = "APM/CrashDump.DAT";
static constexpr uint32_t watchdog_pat_interval = 256U * 1024U;
static constexpr uint32_t retry_delay_min_ms = 30U * 1000U;
static constexpr uint32_t retry_delay_max_ms = 10U * 60U * 1000U;
struct PACKED CrashDumpTrailer {
uint8_t magic[sizeof(crashdump_trailer_magic)];
uint16_t version;
uint16_t size;
uint32_t git_hash;
uint32_t firmware_crc;
uint32_t firmware_size;
uint32_t trailer_crc;
uint32_t dump_size;
};
static_assert(sizeof(CrashDumpTrailer) == 32U, "Unexpected crashdump trailer size");
extern "C" {
extern const uint8_t __firmware_crc_start__;
extern const uint8_t __firmware_crc_end__;
extern const uint8_t __firmware_crc_ext_start__;
extern const uint8_t __firmware_crc_ext_end__;
}
struct CrashDumpExtent {
uint32_t start_sector;
uint32_t sector_count;
};
static CrashDumpExtent *sd_extents;
static uint16_t sd_extent_count;
static uint32_t sd_total_sectors;
#if HAL_USE_SDC
static SDCDriver *sd_sdcp;
#elif CRASHDUMP_SD_SPI
extern MMCDriver MMCD1;
static MMCDriver *sd_mmcp;
static ChibiOS::SPIDevice *sd_spi_device;
static SPIDriver *sd_spip;
static struct bouncebuffer_t *sd_spi_bouncebuffer;
static ioline_t sd_spi_cs_line;
static uint32_t sd_spi_low_config1;
static uint32_t sd_spi_low_config2;
static uint32_t sd_spi_high_config1;
static uint32_t sd_spi_high_config2;
static bool sd_spi_block_addresses;
#endif
static uint8_t *sd_dma_buf;
static uint32_t sd_dma_buf_size;
static uint32_t accumulator_offset;
static uint32_t sd_write_offset;
static uint32_t next_watchdog_pat_offset;
static uint32_t sd_dump_size;
static uint32_t sd_firmware_crc;
static uint32_t sd_firmware_size;
static uint32_t sd_firmware_git_hash;
static bool sd_firmware_identity_calculated;
static uint32_t sd_retry_not_before_ms;
static uint32_t sd_retry_delay_ms;
static bool sd_is_ready;
static bool sd_fault_write_available;
static bool sd_write_failed;
static uint32_t min_u32(uint32_t a, uint32_t b)
{
return a < b ? a : b;
}
static uint32_t crashdump_sd_file_size()
{
return HAL_CC_MEMORY_TOTAL_BYTES + CRASHDUMP_OVERHEAD;
}
enum class CrashDumpFileState : uint8_t {
MISSING,
EMPTY,
COMPLETE,
INCOMPLETE,
IO_ERROR,
};
// Keep these values stable as they are printed during early boot diagnostics.
enum class CrashDumpDiagnostic : uint8_t {
RESERVED_INCOMPLETE = 1,
NO_SD_SPI_DEVICE = 2,
SD_SPI_NOT_RUNNING = 3,
NO_SD_SPI_BOUNCEBUFFER = 4,
NO_SD_BOUNCEBUFFER = 5,
SD_BOUNCEBUFFER_TOO_SMALL = 6,
PUBLISH_RESERVED = 7,
OPEN_RESERVED = 8,
RECREATE_RESERVED = 9,
INITIALISE_RESERVED = 10,
SYNC_RESERVED = 11,
INVALID_RESERVED_SIZE = 12,
REOPEN_RESERVED = 13,
EXTENT_WORK_ALLOCATION = 14,
EXTENT_SEEK = 15,
EXTENT_COUNT = 16,
EXTENT_INVALID = 17,
EXTENT_INCOMPLETE = 18,
EXTENT_FINAL_ALLOCATION = 19,
RESERVE_CREATE_ARMED = 20,
FREE_SPACE_QUERY = 21,
INSUFFICIENT_SPACE = 22,
RESERVE_ATTRIBUTE = 23,
};
static bool unlink_if_exists(const char *path)
{
const FRESULT result = f_unlink(path);
return result == FR_OK || result == FR_NO_FILE;
}
static void report_diagnostic(CrashDumpDiagnostic code, uint32_t detail = 0)
{
printf("CrashDumpSD: %u/%u\n", unsigned(code), unsigned(detail));
}
static bool init_failed(CrashDumpDiagnostic code, uint32_t detail = 0)
{
report_diagnostic(code, detail);
if (sd_retry_delay_ms == 0) {
sd_retry_delay_ms = retry_delay_min_ms;
} else {
sd_retry_delay_ms = min_u32(sd_retry_delay_ms * 2U,
retry_delay_max_ms);
}
sd_retry_not_before_ms = AP_HAL::millis() + sd_retry_delay_ms;
return false;
}
static bool retry_deferred()
{
return sd_retry_not_before_ms != 0 &&
int32_t(AP_HAL::millis() - sd_retry_not_before_ms) < 0;
}
static bool reserve_has_space(uint32_t target_size, uint32_t reclaimable_size)
{
DWORD free_clusters;
FATFS *fs;
const FRESULT result = f_getfree("/", &free_clusters, &fs);
if (result != FR_OK || fs == nullptr) {
return init_failed(CrashDumpDiagnostic::FREE_SPACE_QUERY, result);
}
const uint64_t free_bytes =
uint64_t(free_clusters) * fs->csize * MMCSD_BLOCK_SIZE;
const uint64_t available_bytes = free_bytes + reclaimable_size;
if (available_bytes < target_size) {
return init_failed(CrashDumpDiagnostic::INSUFFICIENT_SPACE,
uint32_t(available_bytes / 1024U));
}
return true;
}
/* Validate a CrashCatcher dump and its completion trailer. */
static CrashDumpFileState get_dump_state(const char *path, uint32_t &dump_size)
{
dump_size = 0;
FIL fp;
const FRESULT open_result = f_open(&fp, path, FA_READ);
if (open_result == FR_NO_FILE) {
return CrashDumpFileState::MISSING;
}
if (open_result != FR_OK) {
return CrashDumpFileState::IO_ERROR;
}
const uint32_t file_size = f_size(&fp);
if (file_size < 2U * MMCSD_BLOCK_SIZE ||
(file_size % MMCSD_BLOCK_SIZE) != 0U) {
f_close(&fp);
return CrashDumpFileState::INCOMPLETE;
}
uint8_t signature[2];
UINT bytes_read;
if (f_lseek(&fp, 0) != FR_OK ||
f_read(&fp, signature, sizeof(signature), &bytes_read) != FR_OK ||
bytes_read != sizeof(signature)) {
f_close(&fp);
return CrashDumpFileState::IO_ERROR;
}
if (signature[0] != 0x63 || signature[1] != 0x43) {
f_close(&fp);
return CrashDumpFileState::EMPTY;
}
CrashDumpTrailer trailer;
if (f_lseek(&fp, file_size - sizeof(trailer)) != FR_OK ||
f_read(&fp, &trailer, sizeof(trailer), &bytes_read) != FR_OK ||
bytes_read != sizeof(trailer)) {
f_close(&fp);
return CrashDumpFileState::IO_ERROR;
}
const uint32_t trailer_crc = trailer.trailer_crc;
trailer.trailer_crc = 0;
const bool valid_trailer =
memcmp(trailer.magic, crashdump_trailer_magic, sizeof(trailer.magic)) == 0 &&
trailer.version == CRASHDUMP_TRAILER_VERSION &&
trailer.size == sizeof(trailer) &&
trailer_crc == crc_crc32(0, reinterpret_cast<const uint8_t *>(&trailer),
sizeof(trailer));
const uint32_t max_size = file_size - MMCSD_BLOCK_SIZE;
if (!valid_trailer || trailer.dump_size == 0 || trailer.dump_size > max_size) {
f_close(&fp);
return CrashDumpFileState::INCOMPLETE;
}
uint8_t padding[16];
if (f_lseek(&fp, trailer.dump_size) != FR_OK ||
f_read(&fp, padding, sizeof(padding), &bytes_read) != FR_OK ||
bytes_read != sizeof(padding)) {
f_close(&fp);
return CrashDumpFileState::IO_ERROR;
}
f_close(&fp);
for (uint8_t byte : padding) {
if (byte != 0xFF) {
return CrashDumpFileState::INCOMPLETE;
}
}
dump_size = trailer.dump_size;
return CrashDumpFileState::COMPLETE;
}
/*
Publish a completed dump left in the reserved file by the fault handler.
All directory operations happen at boot, never in the fault handler.
*/
static bool publish_crashdump(bool &reset_reserved)
{
reset_reserved = false;
uint32_t dump_size;
const CrashDumpFileState state = get_dump_state(crashdump_reserved_path, dump_size);
if (state == CrashDumpFileState::IO_ERROR) {
return false;
}
if (state == CrashDumpFileState::INCOMPLETE) {
reset_reserved = true;
report_diagnostic(CrashDumpDiagnostic::RESERVED_INCOMPLETE);
return true;
}
if (state == CrashDumpFileState::COMPLETE) {
if (!unlink_if_exists(crashdump_published_path) ||
f_rename(crashdump_reserved_path, crashdump_published_path) != FR_OK) {
return false;
}
}
return true;
}
static void calculate_firmware_identity()
{
if (sd_firmware_identity_calculated) {
return;
}
const uintptr_t start = reinterpret_cast<uintptr_t>(&__firmware_crc_start__);
const uintptr_t end = reinterpret_cast<uintptr_t>(&__firmware_crc_end__);
const uintptr_t ext_start = reinterpret_cast<uintptr_t>(&__firmware_crc_ext_start__);
const uintptr_t ext_end = reinterpret_cast<uintptr_t>(&__firmware_crc_ext_end__);
sd_firmware_crc = 0;
sd_firmware_size = 0;
const uintptr_t ranges[][2] = {
{start, end},
{ext_start, ext_end},
};
for (const auto &range : ranges) {
uintptr_t address = range[0];
while (address < range[1]) {
const uint32_t size = min_u32(range[1] - address, 32U * 1024U);
sd_firmware_crc = crc_crc32(sd_firmware_crc,
reinterpret_cast<const uint8_t *>(address), size);
sd_firmware_size += size;
address += size;
stm32_watchdog_pat();
}
}
sd_firmware_git_hash = AP::fwversion().fw_hash;
sd_firmware_identity_calculated = true;
}
/*
Convert a file sector offset to an SD sector and return the number of
physically contiguous sectors remaining in the extent.
*/
static bool sector_mapping(uint32_t sector_offset, uint32_t &sector, uint32_t &available)
{
for (uint16_t i = 0; i < sd_extent_count; i++) {
if (sector_offset < sd_extents[i].sector_count) {
sector = sd_extents[i].start_sector + sector_offset;
available = sd_extents[i].sector_count - sector_offset;
return true;
}
sector_offset -= sd_extents[i].sector_count;
}
return false;
}
/* Build sector extents by walking the file one cluster at a time. */
static bool build_extent_list(FIL &fp, uint32_t num_sectors)
{
CrashDumpExtent *const max_extents = NEW_NOTHROW CrashDumpExtent[CRASHDUMP_SD_MAX_EXTENTS];
if (max_extents == nullptr) {
return init_failed(CrashDumpDiagnostic::EXTENT_WORK_ALLOCATION);
}
FATFS *const fs = fp.obj.fs;
uint16_t extent_count = 0;
uint32_t total_sectors = 0;
uint32_t sectors_remaining = num_sectors;
while (sectors_remaining > 0) {
const uint32_t sector_count = min_u32(fs->csize, sectors_remaining);
// FatFs leaves fp.clust at the cluster containing seek_offset - 1.
// Cluster-end seeks are sector-aligned and advance the chain linearly.
const FSIZE_t seek_offset = FSIZE_t(total_sectors + sector_count) * MMCSD_BLOCK_SIZE;
const FRESULT seek_result = f_lseek(&fp, seek_offset);
if (seek_result != FR_OK || f_tell(&fp) != seek_offset) {
delete[] max_extents;
return init_failed(CrashDumpDiagnostic::EXTENT_SEEK, seek_result);
}
const uint32_t cluster = fp.clust;
if (cluster < 2U || cluster >= fs->n_fatent) {
delete[] max_extents;
return init_failed(CrashDumpDiagnostic::EXTENT_INVALID);
}
const uint32_t start_sector = fs->database +
(cluster - 2U) * fs->csize;
if (extent_count > 0 &&
max_extents[extent_count - 1U].start_sector +
max_extents[extent_count - 1U].sector_count == start_sector) {
max_extents[extent_count - 1U].sector_count += sector_count;
} else {
if (extent_count >= CRASHDUMP_SD_MAX_EXTENTS) {
delete[] max_extents;
return init_failed(CrashDumpDiagnostic::EXTENT_COUNT,
CRASHDUMP_SD_MAX_EXTENTS);
}
max_extents[extent_count++] = {start_sector, sector_count};
}
total_sectors += sector_count;
sectors_remaining -= sector_count;
stm32_watchdog_pat();
}
if (extent_count == 0) {
delete[] max_extents;
return init_failed(CrashDumpDiagnostic::EXTENT_INCOMPLETE);
}
sd_extents = NEW_NOTHROW CrashDumpExtent[extent_count];
if (sd_extents == nullptr) {
delete[] max_extents;
return init_failed(CrashDumpDiagnostic::EXTENT_FINAL_ALLOCATION);
}
memcpy(sd_extents, max_extents, extent_count * sizeof(*sd_extents));
sd_extent_count = extent_count;
sd_total_sectors = total_sectors;
delete[] max_extents;
return true;
}
#if CRASHDUMP_SD_SPI
#define CRASHDUMP_SPI_BYTE_TIMEOUT 100000U
#define CRASHDUMP_SPI_RESPONSE_BYTES 16U
#define CRASHDUMP_SPI_BUSY_BYTES 2000000U
static bool spi_exchange(uint8_t tx, uint8_t &rx)
{
SPI_TypeDef *const spi = sd_spip->spi;
uint32_t timeout = CRASHDUMP_SPI_BYTE_TIMEOUT;
#if defined(STM32H7)
while ((spi->CR1 & SPI_CR1_CSTART) != 0U) {
if (--timeout == 0U) {
return false;
}
}
spi->IFCR = 0xFFFFFFFFU;
spi->CR1 |= SPI_CR1_CSTART;
timeout = CRASHDUMP_SPI_BYTE_TIMEOUT;
while ((spi->SR & SPI_SR_TXP) == 0U) {
if (--timeout == 0U) {
return false;
}
}
*reinterpret_cast<volatile uint8_t *>(&spi->TXDR) = tx;
timeout = CRASHDUMP_SPI_BYTE_TIMEOUT;
while ((spi->SR & SPI_SR_RXP) == 0U) {
if (--timeout == 0U) {
return false;
}
}
rx = *reinterpret_cast<volatile uint8_t *>(&spi->RXDR);
spi->CR1 |= SPI_CR1_CSUSP;
#else
while ((spi->SR & SPI_SR_TXE) == 0U) {
if (--timeout == 0U) {
return false;
}
}
*reinterpret_cast<volatile uint8_t *>(&spi->DR) = tx;
timeout = CRASHDUMP_SPI_BYTE_TIMEOUT;
while ((spi->SR & SPI_SR_RXNE) == 0U) {
if (--timeout == 0U) {
return false;
}
}
rx = *reinterpret_cast<volatile uint8_t *>(&spi->DR);
#endif
return true;
}
static bool spi_send(uint8_t value)
{
uint8_t ignored;
return spi_exchange(value, ignored);
}
static bool spi_clock_bytes(uint32_t count)
{
while (count-- > 0U) {
if (!spi_send(0xFFU)) {
return false;
}
}
return true;
}
static void spi_select()
{
palClearLine(sd_spi_cs_line);
}
static void spi_unselect()
{
palSetLine(sd_spi_cs_line);
(void)spi_send(0xFFU);
}
static bool spi_wait_idle()
{
for (uint32_t i = 0; i < CRASHDUMP_SPI_BUSY_BYTES; i++) {
uint8_t response;
if (!spi_exchange(0xFFU, response)) {
return false;
}
if (response == 0xFFU) {
return true;
}
}
return false;
}
static uint8_t spi_command_crc(uint8_t command)
{
if (command == MMCSD_CMD_GO_IDLE_STATE) {
return 0x95U;
}
if (command == MMCSD_CMD_SEND_IF_COND) {
return 0x87U;
}
return 0x01U;
}
static bool spi_command_selected(uint8_t command, uint32_t argument,
uint8_t &r1, uint8_t *extra = nullptr,
uint8_t extra_length = 0)
{
const uint8_t header[6] = {
uint8_t(0x40U | command),
uint8_t(argument >> 24U),
uint8_t(argument >> 16U),
uint8_t(argument >> 8U),
uint8_t(argument),
spi_command_crc(command),
};
for (uint8_t byte : header) {
if (!spi_send(byte)) {
return false;
}
}
r1 = 0xFFU;
for (uint8_t i = 0; i < CRASHDUMP_SPI_RESPONSE_BYTES; i++) {
if (!spi_exchange(0xFFU, r1)) {
return false;
}
if ((r1 & 0x80U) == 0U) {
break;
}
}
if ((r1 & 0x80U) != 0U) {
return false;
}
for (uint8_t i = 0; i < extra_length; i++) {
if (!spi_exchange(0xFFU, extra[i])) {
return false;
}
}
return true;
}
static bool spi_command(uint8_t command, uint32_t argument, uint8_t &r1,
uint8_t *extra = nullptr, uint8_t extra_length = 0)
{
spi_select();
const bool idle = command == MMCSD_CMD_GO_IDLE_STATE || spi_wait_idle();
const bool success = idle && spi_command_selected(command, argument, r1,
extra, extra_length);
spi_unselect();
return success;
}
static void spi_configure(uint32_t config1, uint32_t config2)
{
SPI_TypeDef *const spi = sd_spip->spi;
#if defined(STM32H7)
spi->CR1 &= ~SPI_CR1_SPE;
spi->CR1 = SPI_CR1_MASRX;
spi->CR2 = 0U;
spi->CFG1 = config1 & ~(SPI_CFG1_FTHLV_Msk | SPI_CFG1_RXDMAEN |
SPI_CFG1_TXDMAEN);
spi->CFG2 = (config2 | SPI_CFG2_MASTER | SPI_CFG2_SSOE) &
~SPI_CFG2_COMM_Msk;
spi->IER = 0U;
spi->IFCR = 0xFFFFFFFFU;
spi->CR1 |= SPI_CR1_SPE;
#else
spi->CR2 = 0U;
spi->CR1 &= ~SPI_CR1_SPE;
(void)spi->DR;
(void)spi->SR;
spi->CR1 = config1 | SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI;
#if defined(SPI_CR2_FRXTH)
config2 |= SPI_CR2_FRXTH;
#endif
spi->CR2 = config2 & ~(SPI_CR2_RXDMAEN | SPI_CR2_TXDMAEN);
spi->CR1 |= SPI_CR1_SPE;
#endif
}
static void spi_disable_dma()
{
#if defined(STM32H7)
#if defined(STM32_SPI_DMA_REQUIRED) && defined(STM32_SPI_BDMA_REQUIRED)
if (sd_spip->is_bdma) {
if (sd_spip->tx.bdma != nullptr) {
nvicDisableVector(sd_spip->tx.bdma->vector);
bdmaStreamDisable(sd_spip->tx.bdma);
}
if (sd_spip->rx.bdma != nullptr) {
nvicDisableVector(sd_spip->rx.bdma->vector);
bdmaStreamDisable(sd_spip->rx.bdma);
}
} else {
if (sd_spip->tx.dma != nullptr) {
nvicDisableVector(sd_spip->tx.dma->vector);
dmaStreamDisable(sd_spip->tx.dma);
}
if (sd_spip->rx.dma != nullptr) {
nvicDisableVector(sd_spip->rx.dma->vector);
dmaStreamDisable(sd_spip->rx.dma);
}
}
#elif defined(STM32_SPI_BDMA_REQUIRED)
if (sd_spip->tx.bdma != nullptr) {
nvicDisableVector(sd_spip->tx.bdma->vector);
bdmaStreamDisable(sd_spip->tx.bdma);
}
if (sd_spip->rx.bdma != nullptr) {
nvicDisableVector(sd_spip->rx.bdma->vector);
bdmaStreamDisable(sd_spip->rx.bdma);
}
#elif defined(STM32_SPI_DMA_REQUIRED)
if (sd_spip->tx.dma != nullptr) {
nvicDisableVector(sd_spip->tx.dma->vector);
dmaStreamDisable(sd_spip->tx.dma);
}
if (sd_spip->rx.dma != nullptr) {
nvicDisableVector(sd_spip->rx.dma->vector);
dmaStreamDisable(sd_spip->rx.dma);
}
#endif
#else
if (sd_spip->dmatx != nullptr) {
nvicDisableVector(sd_spip->dmatx->vector);
dmaStreamDisable(sd_spip->dmatx);
}
if (sd_spip->dmarx != nullptr) {
nvicDisableVector(sd_spip->dmarx->vector);
dmaStreamDisable(sd_spip->dmarx);
}
#endif
}
static void spi_prepare_peripheral()
{
sd_spi_device->crashdump_prepare_peripheral();
spi_disable_dma();
bouncebuffer_abort(sd_spi_bouncebuffer);
sd_spi_device->crashdump_deassert_all_cs();
spi_configure(sd_spi_low_config1, sd_spi_low_config2);
sd_spi_device->crashdump_restore_sck();
}
static bool spi_reconnect_card()
{
palSetLine(sd_spi_cs_line);
if (!spi_clock_bytes(16U)) {
return false;
}
uint8_t r1 = 0xFFU;
bool idle = false;
for (uint8_t retry = 0; retry < 10U; retry++) {
if (spi_command(MMCSD_CMD_GO_IDLE_STATE, 0U, r1) && r1 == 0x01U) {
idle = true;
break;
}
}
if (!idle) {
return false;
}
uint8_t response[4];
if (!spi_command(MMCSD_CMD_SEND_IF_COND, MMCSD_CMD8_PATTERN, r1,
response, sizeof(response)) ||
(r1 != 0x01U && r1 != 0x05U)) {
return false;
}
sd_spi_block_addresses = false;
if (r1 != 0x05U) {
bool ready = false;
for (uint32_t retry = 0; retry < 10000U; retry++) {
if (spi_command(MMCSD_CMD_APP_CMD, 0U, r1) && r1 <= 0x01U &&
spi_command(MMCSD_CMD_APP_OP_COND, 0x400001AAU, r1) && r1 == 0x00U) {
ready = true;
break;
}
}
if (!ready ||
!spi_command(MMCSD_CMD_READ_OCR, 0U, r1, response, sizeof(response)) ||
r1 != 0x00U) {
return false;
}
sd_spi_block_addresses = (response[0] & 0x40U) != 0U;
} else {
bool ready = false;
for (uint32_t retry = 0; retry < 10000U; retry++) {
if (spi_command(MMCSD_CMD_INIT, 0U, r1) && r1 == 0x00U) {
ready = true;
break;
}
}
if (!ready) {
return false;
}
}
spi_configure(sd_spi_high_config1, sd_spi_high_config2);
return spi_command(MMCSD_CMD_SET_BLOCKLEN, MMCSD_BLOCK_SIZE, r1) && r1 == 0x00U;
}
static uint32_t spi_card_address(uint32_t sector)
{
return sd_spi_block_addresses ? sector : sector * MMCSD_BLOCK_SIZE;
}
static bool wait_for_transfer_state()
{
uint8_t r1;
uint8_t r2;
return spi_command(MMCSD_CMD_SEND_STATUS, 0U, r1, &r2, 1U) &&
r1 == 0x00U && r2 == 0x00U;
}
static bool abort_transfer()
{
spi_prepare_peripheral();
return spi_reconnect_card();
}
static bool write_blocks(uint32_t sector, uint32_t blocks)
{
const bool multiple = blocks > 1U;
spi_select();
uint8_t r1;
bool success = spi_wait_idle() &&
spi_command_selected(multiple ? MMCSD_CMD_WRITE_MULTIPLE_BLOCK :
MMCSD_CMD_WRITE_BLOCK,
spi_card_address(sector), r1) &&
r1 == 0x00U;
const uint8_t *buffer = sd_dma_buf;
for (uint32_t block = 0; success && block < blocks; block++) {
success = spi_send(multiple ? 0xFCU : 0xFEU);
for (uint32_t i = 0; success && i < MMCSD_BLOCK_SIZE; i++) {
success = spi_send(buffer[i]);
}
success = success && spi_clock_bytes(2U);
uint8_t response = 0xFFU;
success = success && spi_exchange(0xFFU, response) &&
(response & 0x1FU) == 0x05U && spi_wait_idle();
buffer += MMCSD_BLOCK_SIZE;
}
if (multiple) {
success = spi_send(0xFDU) && spi_wait_idle() && success;
}
spi_unselect();
return success;
}
#else // CRASHDUMP_SD_SPI
#define CRASHDUMP_SDC_COMMAND_POLL_LIMIT 10000000U
#define CRASHDUMP_SDC_TRANSFER_STATE_POLLS 1000000U
#define CRASHDUMP_SDC_DATA_POLL_LIMIT 100000000U
/*
ChibiOS' command helpers have no software bound around their peripheral
status loops. Keep the crash-time path bounded in case the controller is
wedged rather than relying solely on its command timeout flag.
*/
static bool send_command_short_crc(uint8_t command, uint32_t argument,
uint32_t &response)
{
uint32_t status = 0;
#if CRASHDUMP_SD_SDMMCV2 || CRASHDUMP_SD_SDMMCV1
const uint32_t success_flag = SDMMC_STA_CMDREND;
const uint32_t error_flags = SDMMC_STA_CTIMEOUT | SDMMC_STA_CCRCFAIL;
const uint32_t completion_flags = success_flag | error_flags;
sd_sdcp->sdmmc->ARG = argument;
sd_sdcp->sdmmc->CMD = uint32_t(command) | SDMMC_CMD_WAITRESP_0 |
SDMMC_CMD_CPSMEN;
#elif CRASHDUMP_SD_SDIOV1
const uint32_t success_flag = SDIO_STA_CMDREND;
const uint32_t error_flags = SDIO_STA_CTIMEOUT | SDIO_STA_CCRCFAIL;
const uint32_t completion_flags = success_flag | error_flags;
sd_sdcp->sdio->ARG = argument;
sd_sdcp->sdio->CMD = uint32_t(command) | SDIO_CMD_WAITRESP_0 |
SDIO_CMD_CPSMEN;
#endif
for (uint32_t i = 0; i < CRASHDUMP_SDC_COMMAND_POLL_LIMIT; i++) {
#if CRASHDUMP_SD_SDMMCV2 || CRASHDUMP_SD_SDMMCV1
status = sd_sdcp->sdmmc->STA;
#elif CRASHDUMP_SD_SDIOV1
status = sd_sdcp->sdio->STA;
#endif
if ((status & completion_flags) != 0U) {
break;
}
}
#if CRASHDUMP_SD_SDMMCV2 || CRASHDUMP_SD_SDMMCV1
if ((status & completion_flags) == 0U) {
sd_sdcp->sdmmc->CMD = 0U;
}
sd_sdcp->sdmmc->ICR = status & completion_flags;
response = sd_sdcp->sdmmc->RESP1;
#elif CRASHDUMP_SD_SDIOV1
if ((status & completion_flags) == 0U) {
sd_sdcp->sdio->CMD = 0U;
}
sd_sdcp->sdio->ICR = status & completion_flags;
response = sd_sdcp->sdio->RESP1;
#endif
return (status & success_flag) != 0U && (status & error_flags) == 0U;
}
/*
Poll for the card transfer state without sleeping or taking an RTOS lock.
ChibiOS' command primitive is itself polling-only.
*/
static bool wait_for_transfer_state()
{
bool ignore_first_error = true;
for (uint32_t i = 0; i < CRASHDUMP_SDC_TRANSFER_STATE_POLLS; i++) {
uint32_t response;
const bool command_ok = send_command_short_crc(
MMCSD_CMD_SEND_STATUS, sd_sdcp->rca, response);
if (!command_ok || MMCSD_R1_ERROR(response)) {
if (ignore_first_error) {
ignore_first_error = false;
continue;
}
return false;
}
ignore_first_error = false;
if (MMCSD_R1_STS(response) == MMCSD_STS_TRAN) {
return true;
}
}
return false;
}
static uint32_t data_timeout_ticks(uint32_t timeout_ms)
{
#if CRASHDUMP_SD_SDMMCV2
const uint32_t divider = (sd_sdcp->sdmmc->CLKCR & SDMMC_CLKCR_CLKDIV_Msk) + 1U;
return ((sd_sdcp->clkfreq / (divider * 2U)) / 1000U) * timeout_ms;
#elif CRASHDUMP_SD_SDMMCV1
const uint32_t clkcr = sd_sdcp->sdmmc->CLKCR;
const uint32_t divider = (clkcr & SDMMC_CLKCR_BYPASS) != 0U ?
1U : (clkcr & SDMMC_CLKCR_CLKDIV_Msk) + 2U;
return ((sd_sdcp->clkfreq / (divider * 2U)) / 1000U) * timeout_ms;
#elif CRASHDUMP_SD_SDIOV1
const uint32_t clkcr = sd_sdcp->sdio->CLKCR;
const uint32_t divider = (clkcr & SDIO_CLKCR_BYPASS) != 0U ?
1U : (clkcr & SDIO_CLKCR_CLKDIV_Msk) + 2U;
return ((48000000U / (divider * 2U)) / 1000U) * timeout_ms;
#endif
}
static void stop_data_path()
{
#if CRASHDUMP_SD_SDMMCV2
sd_sdcp->sdmmc->IDMACTRL = 0U;
sd_sdcp->sdmmc->MASK = 0U;
sd_sdcp->sdmmc->DCTRL = 0U;
#elif CRASHDUMP_SD_SDMMCV1
sd_sdcp->sdmmc->MASK = 0U;
if (sd_sdcp->dma != nullptr) {
dmaStreamDisable(sd_sdcp->dma);
}
sd_sdcp->sdmmc->DCTRL = 0U;
#elif CRASHDUMP_SD_SDIOV1
sd_sdcp->sdio->MASK = 0U;
if (sd_sdcp->dma != nullptr) {
dmaStreamDisable(sd_sdcp->dma);
}
sd_sdcp->sdio->DCTRL = 0U;
#endif
}
static void clear_data_flags()
{
#if CRASHDUMP_SD_SDMMCV2
sd_sdcp->sdmmc->ICR = SDMMC_ICR_ALL_FLAGS;
#elif CRASHDUMP_SD_SDMMCV1
sd_sdcp->sdmmc->ICR = SDMMC_ICR_ALL_FLAGS;
#elif CRASHDUMP_SD_SDIOV1
sd_sdcp->sdio->ICR = CRASHDUMP_SDIO_ICR_ALL_FLAGS;
#endif
}
static bool stop_multiblock_transfer()
{
uint32_t response;
return send_command_short_crc(MMCSD_CMD_STOP_TRANSMISSION, 0, response);
}
static bool wait_for_data_end(uint32_t blocks)
{
uint32_t status = 0;
#if CRASHDUMP_SD_SDMMCV2
for (uint32_t i = 0; i < CRASHDUMP_SDC_DATA_POLL_LIMIT; i++) {
status = sd_sdcp->sdmmc->STA;
if ((status & (SDMMC_STA_DATAEND | SDMMC_DATA_ERROR_FLAGS)) != 0) {
break;
}
}
stop_data_path();
clear_data_flags();
const bool success = (status & SDMMC_STA_DATAEND) != 0 &&
(status & SDMMC_DATA_ERROR_FLAGS) == 0;
#elif CRASHDUMP_SD_SDMMCV1
bool dma_complete = false;
for (uint32_t i = 0; i < CRASHDUMP_SDC_DATA_POLL_LIMIT; i++) {
status = sd_sdcp->sdmmc->STA;
if ((status & SDMMC_DATA_ERROR_FLAGS) != 0U) {
break;
}
// F7 uses DMAv2 peripheral flow control; match dmaWaitCompletion().
if ((status & SDMMC_STA_DATAEND) != 0U &&
(sd_sdcp->dma->stream->CR & STM32_DMA_CR_EN) == 0U) {
dma_complete = true;
break;
}
}
stop_data_path();
clear_data_flags();
const bool success = dma_complete &&
(status & SDMMC_STA_DATAEND) != 0U &&
(status & SDMMC_DATA_ERROR_FLAGS) == 0U;
#elif CRASHDUMP_SD_SDIOV1
bool dma_complete = false;
for (uint32_t i = 0; i < CRASHDUMP_SDC_DATA_POLL_LIMIT; i++) {
status = sd_sdcp->sdio->STA;
if ((status & SDIO_DATA_ERROR_FLAGS) != 0U) {
break;
}
if ((status & SDIO_STA_DATAEND) != 0U &&
dmaStreamGetTransactionSize(sd_sdcp->dma) == 0U) {
dma_complete = true;
break;
}
}
stop_data_path();
clear_data_flags();
const bool success = dma_complete &&
(status & SDIO_STA_DATAEND) != 0U &&
(status & SDIO_DATA_ERROR_FLAGS) == 0U;
#endif
const bool stopped = blocks <= 1U || stop_multiblock_transfer();
return success && stopped;
}
static bool abort_transfer()
{
#if CRASHDUMP_SD_SDMMCV2 || CRASHDUMP_SD_SDMMCV1
#if defined(STM32_SDC_USE_SDMMC1) && STM32_SDC_USE_SDMMC1 == TRUE
if (sd_sdcp == &SDCD1) {
nvicDisableVector(STM32_SDMMC1_NUMBER);
}
#endif
#if defined(STM32_SDC_USE_SDMMC2) && STM32_SDC_USE_SDMMC2 == TRUE
if (sd_sdcp == &SDCD2) {
nvicDisableVector(STM32_SDMMC2_NUMBER);
}
#endif
#if CRASHDUMP_SD_SDMMCV1
if (sd_sdcp->dma != nullptr) {
nvicDisableVector(sd_sdcp->dma->vector);
}
#endif
#elif CRASHDUMP_SD_SDIOV1
nvicDisableVector(STM32_SDIO_NUMBER);
if (sd_sdcp->dma != nullptr) {
nvicDisableVector(sd_sdcp->dma->vector);
}
#endif
stop_data_path();
clear_data_flags();
for (volatile uint32_t i = 0; i < 1000U; i++) {
}
for (uint8_t i = 0; i < 3U; i++) {
(void)stop_multiblock_transfer();
clear_data_flags();
}
stop_data_path();
clear_data_flags();
bouncebuffer_abort(sd_sdcp->bouncebuffer);
return true;
}
static bool prepare_write_transfer(uint32_t start_sector, uint32_t blocks)
{
uint32_t card_address = start_sector;
if ((sd_sdcp->cardmode & SDC_MODE_HIGH_CAPACITY) == 0) {
card_address *= MMCSD_BLOCK_SIZE;
}
#if CRASHDUMP_SD_SDMMCV2
const uint32_t timeout_ms = STM32_SDC_SDMMC_WRITE_TIMEOUT;
sd_sdcp->sdmmc->DTIMER = data_timeout_ticks(timeout_ms);
#elif CRASHDUMP_SD_SDMMCV1
const uint32_t timeout_ms = STM32_SDC_SDMMC_WRITE_TIMEOUT;
sd_sdcp->sdmmc->DTIMER = data_timeout_ticks(timeout_ms);
#elif CRASHDUMP_SD_SDIOV1
const uint32_t timeout_ms = STM32_SDC_WRITE_TIMEOUT_MS;
sd_sdcp->sdio->DTIMER = data_timeout_ticks(timeout_ms);
#endif
if (!wait_for_transfer_state()) {
return false;
}
#if CRASHDUMP_SD_SDMMCV2
sd_sdcp->sdmmc->IDMABASE0 = reinterpret_cast<uint32_t>(sd_dma_buf);
sd_sdcp->sdmmc->IDMACTRL = SDMMC_IDMA_IDMAEN;
sd_sdcp->sdmmc->ICR = SDMMC_ICR_ALL_FLAGS;
sd_sdcp->sdmmc->MASK = 0U;
sd_sdcp->sdmmc->DLEN = blocks * MMCSD_BLOCK_SIZE;
#elif CRASHDUMP_SD_SDMMCV1
if (sd_sdcp->dma == nullptr) {
return false;
}
dmaStreamSetMemory0(sd_sdcp->dma, sd_dma_buf);
dmaStreamSetTransactionSize(sd_sdcp->dma,
blocks * MMCSD_BLOCK_SIZE / sizeof(uint32_t));
dmaStreamSetMode(sd_sdcp->dma, sd_sdcp->dmamode |
STM32_DMA_CR_DIR_M2P);
dmaStreamEnable(sd_sdcp->dma);
sd_sdcp->sdmmc->ICR = SDMMC_ICR_ALL_FLAGS;
sd_sdcp->sdmmc->MASK = 0U;
sd_sdcp->sdmmc->DLEN = blocks * MMCSD_BLOCK_SIZE;
#elif CRASHDUMP_SD_SDIOV1
if (sd_sdcp->dma == nullptr) {
return false;
}
dmaStreamSetMemory0(sd_sdcp->dma, sd_dma_buf);
dmaStreamSetTransactionSize(sd_sdcp->dma,
blocks * MMCSD_BLOCK_SIZE / sizeof(uint32_t));
dmaStreamSetMode(sd_sdcp->dma, sd_sdcp->dmamode |
STM32_DMA_CR_DIR_M2P);
dmaStreamEnable(sd_sdcp->dma);
sd_sdcp->sdio->ICR = CRASHDUMP_SDIO_ICR_ALL_FLAGS;
sd_sdcp->sdio->MASK = 0U;
sd_sdcp->sdio->DLEN = blocks * MMCSD_BLOCK_SIZE;
#endif
const uint8_t command = blocks > 1U ?
MMCSD_CMD_WRITE_MULTIPLE_BLOCK : MMCSD_CMD_WRITE_BLOCK;
uint32_t response;
if (!send_command_short_crc(command, card_address, response) ||
MMCSD_R1_ERROR(response)) {
stop_data_path();
return false;
}
#if CRASHDUMP_SD_SDMMCV2
sd_sdcp->sdmmc->DCTRL = SDMMC_DCTRL_FIFORST |
SDMMC_DCTRL_DBLOCKSIZE_3 |
SDMMC_DCTRL_DBLOCKSIZE_0 |
SDMMC_DCTRL_DTEN;
#elif CRASHDUMP_SD_SDMMCV1
sd_sdcp->sdmmc->DCTRL = SDMMC_DCTRL_DBLOCKSIZE_3 |
SDMMC_DCTRL_DBLOCKSIZE_0 |
SDMMC_DCTRL_DMAEN |
SDMMC_DCTRL_DTEN;
#elif CRASHDUMP_SD_SDIOV1
sd_sdcp->sdio->DCTRL = SDIO_DCTRL_DBLOCKSIZE_3 |
SDIO_DCTRL_DBLOCKSIZE_0 |
SDIO_DCTRL_DMAEN |
SDIO_DCTRL_DTEN;
#endif
return true;
}
static bool write_blocks(uint32_t sector, uint32_t blocks)
{
stm32_cacheBufferFlush(sd_dma_buf, blocks * MMCSD_BLOCK_SIZE);
return prepare_write_transfer(sector, blocks) && wait_for_data_end(blocks);
}
#endif // CRASHDUMP_SD_SPI
/* Refresh the bounce buffer that normal transfers may have resized. */
static bool refresh_dma_buffer()
{
struct bouncebuffer_t *bouncebuffer;
#if CRASHDUMP_SD_SPI
bouncebuffer = sd_spi_bouncebuffer;
#else
bouncebuffer = sd_sdcp == nullptr ? nullptr : sd_sdcp->bouncebuffer;
#endif
if (bouncebuffer == nullptr) {
sd_dma_buf = nullptr;
sd_dma_buf_size = 0;
return false;
}
uint8_t *const dma_buf = bouncebuffer->dma_buf;
const uint32_t size = bouncebuffer->size & ~(MMCSD_BLOCK_SIZE - 1U);
if (dma_buf == nullptr || size < MMCSD_BLOCK_SIZE) {
sd_dma_buf = nullptr;
sd_dma_buf_size = 0;
return false;
}
sd_dma_buf = dma_buf;
sd_dma_buf_size = size;
return true;
}
static uint32_t accumulator_capacity()
{
uint32_t sector;
uint32_t contiguous_sectors;
if (!sector_mapping(sd_write_offset / MMCSD_BLOCK_SIZE,
sector, contiguous_sectors)) {
return 0;
}
if (sd_write_offset >= crashdump_sd_max_size()) {
return 0;
}
return min_u32(min_u32(sd_dma_buf_size, contiguous_sectors * MMCSD_BLOCK_SIZE),
crashdump_sd_max_size() - sd_write_offset);
}
static bool flush_accumulator()
{
if (accumulator_offset == 0 ||
(accumulator_offset % MMCSD_BLOCK_SIZE) != 0) {
return false;
}
uint32_t sector;
uint32_t contiguous_sectors;
if (!sector_mapping(sd_write_offset / MMCSD_BLOCK_SIZE,
sector, contiguous_sectors)) {
return false;
}
const uint32_t blocks = accumulator_offset / MMCSD_BLOCK_SIZE;
if (blocks > contiguous_sectors) {
return false;
}
bool success = false;
for (uint8_t retry = 0; retry < 3U; retry++) {
if (write_blocks(sector, blocks)) {
success = true;
break;
}
}
if (!success) {
return false;
}
sd_write_offset += accumulator_offset;
accumulator_offset = 0;
if (sd_write_offset >= next_watchdog_pat_offset) {
stm32_watchdog_pat();
next_watchdog_pat_offset += watchdog_pat_interval;
}
return true;
}
bool crashdump_sd_init()
{
if (retry_deferred()) {
return false;
}
sd_is_ready = false;
sd_fault_write_available = false;
sd_dump_size = 0;
delete[] sd_extents;
sd_extents = nullptr;
sd_extent_count = 0;
sd_total_sectors = 0;
#if CRASHDUMP_SD_SPI
sd_mmcp = &MMCD1;
AP_HAL::SPIDevice *const hal_device = sdcard_get_spi_device();
if (hal_device == nullptr) {
return init_failed(CrashDumpDiagnostic::NO_SD_SPI_DEVICE);
}
sd_spi_device = static_cast<ChibiOS::SPIDevice *>(hal_device);
sd_spip = sd_spi_device->get_driver();
if (sd_spip == nullptr || sd_spip->config == nullptr ||
sd_mmcp->config == nullptr || sd_mmcp->config->lscfg == nullptr ||
sd_mmcp->config->hscfg == nullptr) {
return init_failed(CrashDumpDiagnostic::SD_SPI_NOT_RUNNING);
}
sd_spi_cs_line = sd_spi_device->get_chip_select_line();
sd_spi_device->get_crashdump_config(false, sd_spi_low_config1,
sd_spi_low_config2);
sd_spi_device->get_crashdump_config(true, sd_spi_high_config1,
sd_spi_high_config2);
sd_spi_block_addresses = sd_mmcp->block_addresses;
for (uint32_t size = AP_FATFS_MAX_IO_SIZE;
size >= MMCSD_BLOCK_SIZE;
size /= 2U) {
sd_spi_bouncebuffer = sd_spi_device->prepare_crashdump_buffer(size);
if (sd_spi_bouncebuffer != nullptr &&
sd_spi_bouncebuffer->dma_buf != nullptr) {
break;
}
}
if (sd_spi_bouncebuffer == nullptr ||
sd_spi_bouncebuffer->dma_buf == nullptr) {
return init_failed(CrashDumpDiagnostic::NO_SD_SPI_BOUNCEBUFFER);
}
sd_dma_buf = sd_spi_bouncebuffer->dma_buf;
sd_dma_buf_size = sd_spi_bouncebuffer->size & ~(MMCSD_BLOCK_SIZE - 1U);
#else
#if defined(STM32_SDC_USE_SDMMC2) && STM32_SDC_USE_SDMMC2 == TRUE
sd_sdcp = &SDCD2;
#else
sd_sdcp = &SDCD1;
#endif
if (sd_sdcp->bouncebuffer == nullptr ||
sd_sdcp->bouncebuffer->dma_buf == nullptr) {
return init_failed(CrashDumpDiagnostic::NO_SD_BOUNCEBUFFER);
}
sd_dma_buf = sd_sdcp->bouncebuffer->dma_buf;
sd_dma_buf_size = sd_sdcp->bouncebuffer->size & ~(MMCSD_BLOCK_SIZE - 1U);
#endif
if (sd_dma_buf_size < MMCSD_BLOCK_SIZE) {
return init_failed(CrashDumpDiagnostic::SD_BOUNCEBUFFER_TOO_SMALL,
sd_dma_buf_size);
}
const uint32_t target_size = crashdump_sd_file_size();
(void)f_mkdir("APM");
FRESULT result = f_chmod(crashdump_reserved_path, 0, AM_RDO);
if (result != FR_OK && result != FR_NO_FILE) {
return init_failed(CrashDumpDiagnostic::RESERVE_ATTRIBUTE, result);
}
bool reset_reserved;
if (!publish_crashdump(reset_reserved)) {
return init_failed(CrashDumpDiagnostic::PUBLISH_RESERVED);
}
const bool armed = hal.util->get_soft_armed();
bool new_reserved = false;
FIL fp;
result = f_open(&fp, crashdump_reserved_path,
FA_OPEN_EXISTING | FA_READ | FA_WRITE);
if (result == FR_NO_FILE) {
if (armed) {
return init_failed(CrashDumpDiagnostic::RESERVE_CREATE_ARMED);
}
result = f_open(&fp, crashdump_reserved_path,
FA_CREATE_ALWAYS | FA_READ | FA_WRITE);
new_reserved = result == FR_OK;
}
if (result != FR_OK) {
return init_failed(CrashDumpDiagnostic::OPEN_RESERVED, result);
}
if (reset_reserved || new_reserved || f_size(&fp) != target_size) {
if (armed) {
f_close(&fp);
return init_failed(CrashDumpDiagnostic::RESERVE_CREATE_ARMED);
}
const uint32_t reclaimable_size = f_size(&fp);
if (!reserve_has_space(target_size, reclaimable_size)) {
f_close(&fp);
return false;
}
if (!new_reserved) {
f_close(&fp);
result = f_open(&fp, crashdump_reserved_path,
FA_CREATE_ALWAYS | FA_READ | FA_WRITE);
if (result != FR_OK) {
return init_failed(CrashDumpDiagnostic::RECREATE_RESERVED, result);
}
}
memset(sd_dma_buf, 0xFF, sd_dma_buf_size);
for (uint32_t offset = 0; offset < target_size; offset += sd_dma_buf_size) {
const UINT chunk = min_u32(sd_dma_buf_size, target_size - offset);
UINT bytes_written;
result = f_write(&fp, sd_dma_buf, chunk, &bytes_written);
if (result != FR_OK || bytes_written != chunk) {
f_close(&fp);
return init_failed(CrashDumpDiagnostic::INITIALISE_RESERVED, result);
}
stm32_watchdog_pat();
}
stm32_watchdog_pat();
result = f_sync(&fp);
if (result != FR_OK) {
f_close(&fp);
return init_failed(CrashDumpDiagnostic::SYNC_RESERVED, result);
}
stm32_watchdog_pat();
}
const uint32_t file_size = f_size(&fp);
f_close(&fp);
if (file_size < 2U * MMCSD_BLOCK_SIZE ||
(file_size % MMCSD_BLOCK_SIZE) != 0U) {
return init_failed(CrashDumpDiagnostic::INVALID_RESERVED_SIZE, file_size);
}
result = f_open(&fp, crashdump_reserved_path, FA_READ);
if (result != FR_OK) {
return init_failed(CrashDumpDiagnostic::REOPEN_RESERVED, result);
}
const uint8_t filesystem_type = fp.obj.fs->fs_type;
const bool extent_list_ok = build_extent_list(fp, file_size / MMCSD_BLOCK_SIZE);
f_close(&fp);
if (!extent_list_ok) {
return false;
}
result = f_chmod(crashdump_reserved_path, AM_RDO, AM_RDO);
if (result != FR_OK) {
return init_failed(CrashDumpDiagnostic::RESERVE_ATTRIBUTE, result);
}
calculate_firmware_identity();
if (get_dump_state(crashdump_published_path, sd_dump_size) !=
CrashDumpFileState::COMPLETE) {
sd_dump_size = 0;
}
printf("CrashDumpSD: fs %u, %u extents, %u sectors, %u byte buffer, firmware %08x/%u\n",
unsigned(filesystem_type),
unsigned(sd_extent_count), unsigned(sd_total_sectors),
unsigned(sd_dma_buf_size), unsigned(sd_firmware_crc),
unsigned(sd_firmware_size));
sd_retry_not_before_ms = 0;
sd_retry_delay_ms = 0;
// The reserved file was inspected with FatFs above. It is either the
// existing empty reserve or was recreated after publishing/resetting it.
sd_fault_write_available = true;
sd_is_ready = true;
return true;
}
void crashdump_sd_invalidate()
{
sd_is_ready = false;
sd_fault_write_available = false;
sd_retry_not_before_ms = 0;
sd_retry_delay_ms = 0;
}
bool crashdump_sd_ready()
{
return sd_is_ready;
}
uint32_t crashdump_sd_max_size()
{
if (sd_total_sectors < 2U) {
return 0;
}
return (sd_total_sectors - 1U) * MMCSD_BLOCK_SIZE;
}
bool crashdump_sd_start()
{
if (!sd_is_ready || !sd_fault_write_available
#if CRASHDUMP_SD_SPI
|| sd_mmcp == nullptr || sd_spi_device == nullptr || sd_spip == nullptr
#else
|| sd_sdcp == nullptr
#endif
) {
return false;
}
// Only one fault may consume the reserve between filesystem mounts.
sd_fault_write_available = false;
if (!abort_transfer()) {
return false;
}
if (!refresh_dma_buffer()) {
return false;
}
sd_write_offset = 0;
accumulator_offset = 0;
sd_write_failed = false;
next_watchdog_pat_offset = watchdog_pat_interval;
// Pat before the first write; further pats happen every 256 KiB.
stm32_watchdog_pat();
return true;
}
static bool crashdump_sd_write_bytes(const uint8_t *data, uint32_t length)
{
if (sd_write_failed) {
return false;
}
while (length > 0) {
const uint32_t capacity = accumulator_capacity();
if (capacity == 0) {
sd_write_failed = true;
return false;
}
const uint32_t space = capacity - accumulator_offset;
const uint32_t chunk = min_u32(length, space);
memmove(&sd_dma_buf[accumulator_offset], data, chunk);
accumulator_offset += chunk;
data += chunk;
length -= chunk;
if (accumulator_offset == capacity && !flush_accumulator()) {
sd_write_failed = true;
return false;
}
}
return true;
}
bool crashdump_sd_write(const void *data,
CrashCatcherElementSizes element_size,
size_t element_count)
{
if (element_size == CRASH_CATCHER_BYTE) {
return crashdump_sd_write_bytes(static_cast<const uint8_t *>(data),
element_count);
}
if (element_size == CRASH_CATCHER_HALFWORD) {
const volatile uint16_t *source =
static_cast<const volatile uint16_t *>(data);
while (element_count-- > 0) {
const uint16_t value = *source++;
if (!crashdump_sd_write_bytes(
reinterpret_cast<const uint8_t *>(&value), sizeof(value))) {
return false;
}
}
return true;
}
if (element_size == CRASH_CATCHER_WORD) {
const volatile uint32_t *source =
static_cast<const volatile uint32_t *>(data);
while (element_count-- > 0) {
const uint32_t value = *source++;
if (!crashdump_sd_write_bytes(
reinterpret_cast<const uint8_t *>(&value), sizeof(value))) {
return false;
}
}
return true;
}
return false;
}
bool crashdump_sd_end(uint32_t dump_size)
{
if (sd_write_failed || dump_size > crashdump_sd_max_size()) {
return false;
}
if (accumulator_offset > 0) {
const uint32_t padded_size = (accumulator_offset + MMCSD_BLOCK_SIZE - 1U) &
~(MMCSD_BLOCK_SIZE - 1U);
memset(&sd_dma_buf[accumulator_offset], 0xFF, padded_size - accumulator_offset);
accumulator_offset = padded_size;
if (!flush_accumulator()) {
return false;
}
}
uint32_t last_sector;
uint32_t available;
if (!sector_mapping(sd_total_sectors - 1U, last_sector, available)) {
return false;
}
memset(sd_dma_buf, 0xFF, MMCSD_BLOCK_SIZE);
CrashDumpTrailer trailer {};
memcpy(trailer.magic, crashdump_trailer_magic, sizeof(trailer.magic));
trailer.version = CRASHDUMP_TRAILER_VERSION;
trailer.size = sizeof(trailer);
trailer.git_hash = sd_firmware_git_hash;
trailer.firmware_crc = sd_firmware_crc;
trailer.firmware_size = sd_firmware_size;
trailer.dump_size = dump_size;
trailer.trailer_crc = crc_crc32(0, reinterpret_cast<const uint8_t *>(&trailer),
sizeof(trailer));
memcpy(&sd_dma_buf[MMCSD_BLOCK_SIZE - sizeof(trailer)], &trailer, sizeof(trailer));
const bool success = write_blocks(last_sector, 1U) && wait_for_transfer_state();
return success;
}
uint32_t crashdump_sd_dump_size()
{
return sd_is_ready ? sd_dump_size : 0;
}
void crashdump_sd_update()
{
if (!sd_is_ready || sd_dump_size == 0) {
return;
}
// MAVFTP can remove the published file without going through this module.
// This runs in the IO thread so arming checks only read cached state.
struct stat st;
errno = 0;
if (AP::FS().stat(crashdump_published_path, &st) != 0 &&
errno == ENOENT) {
sd_dump_size = 0;
}
}
#endif // AP_CRASHDUMP_FATFS_ENABLED && SD card transport