diff --git a/apps/esp32_lvgl/src/esp32_lvgl_idf_app_facade_runtime.cpp b/apps/esp32_lvgl/src/esp32_lvgl_idf_app_facade_runtime.cpp index eafdfb1b..73a4b111 100644 --- a/apps/esp32_lvgl/src/esp32_lvgl_idf_app_facade_runtime.cpp +++ b/apps/esp32_lvgl/src/esp32_lvgl_idf_app_facade_runtime.cpp @@ -33,6 +33,7 @@ #include "platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h" #include "platform/esp/arduino_common/chat/infra/reticulum/reticulum_adapter.h" #include "platform/esp/arduino_common/chat/infra/store/sd_store.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/boards/board_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "platform/esp/radio/meshtastic_radio_adapter.h" @@ -58,14 +59,12 @@ #include "ui/widgets/top_bar_power_presenter.h" #include -#include #include #include #include #include #include #include -#include #include #endif @@ -338,21 +337,23 @@ bool isValidContactBlobSize(size_t len) std::string makeSdPath(const char* relative) { - const char* mount = platform::esp::idf_common::bsp_runtime::sdcard_mount_point(); - std::string path = mount ? mount : ""; - if (path.empty() || !relative || !relative[0]) + if (!relative || !relative[0]) { - return path; + return "/"; } - if (path.back() == '/' && relative[0] == '/') + std::string path = relative; + if (path.size() >= 2 && (path[0] == 'A' || path[0] == 'a') && path[1] == ':') { - path.pop_back(); + path.erase(0, 2); } - else if (path.back() != '/' && relative[0] != '/') + if (path.empty()) { - path.push_back('/'); + return "/"; + } + if (path.front() != '/') + { + path.insert(path.begin(), '/'); } - path += relative; return path; } @@ -366,46 +367,42 @@ bool readSdFile(const char* relative, std::vector& out, size_t max_len) } const std::string path = makeSdPath(relative); - FILE* file = std::fopen(path.c_str(), "rb"); - if (!file) + platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(path.c_str(), "rb")) { return false; } - if (std::fseek(file, 0, SEEK_END) != 0) + const uint64_t file_size = file.size(); + if (file_size == 0 || file_size > max_len) { - std::fclose(file); + file.close(); return false; } - const long size = std::ftell(file); - if (size <= 0 || static_cast(size) > max_len) + if (!file.seek(0)) { - std::fclose(file); - return false; - } - if (std::fseek(file, 0, SEEK_SET) != 0) - { - std::fclose(file); + file.close(); return false; } - out.reserve(static_cast(size)); + const size_t size = static_cast(file_size); + out.reserve(size); uint8_t buffer[kIdfReadChunkBytes]; size_t total_read = 0; - while (total_read < static_cast(size)) + while (total_read < size) { - const size_t chunk = std::min(kIdfReadChunkBytes, static_cast(size) - total_read); - const size_t read = std::fread(buffer, 1, chunk, file); - if (read != chunk) + const size_t chunk = std::min(kIdfReadChunkBytes, size - total_read); + const int read = file.read(buffer, chunk); + if (read < 0 || static_cast(read) != chunk) { - std::fclose(file); + file.close(); out.clear(); return false; } - out.insert(out.end(), buffer, buffer + read); - total_read += read; + out.insert(out.end(), buffer, buffer + static_cast(read)); + total_read += static_cast(read); } - std::fclose(file); + file.close(); return true; } @@ -417,8 +414,8 @@ bool removeSdFile(const char* relative) } const std::string path = makeSdPath(relative); const std::string temp_path = path + ".tmp"; - std::remove(temp_path.c_str()); - std::remove(path.c_str()); + (void)platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); + (void)platform::esp::arduino_common::storage::sd_remove(path.c_str()); return true; } @@ -442,32 +439,33 @@ bool writeSdFileAtomic(const char* relative, const uint8_t* data, size_t len) const std::string path = makeSdPath(relative); const std::string temp_path = path + ".tmp"; - std::remove(temp_path.c_str()); + (void)platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); - FILE* file = std::fopen(temp_path.c_str(), "wb"); - if (!file) + platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(temp_path.c_str(), "wb")) { ESP_LOGW(kIdfStoreTag, "save open failed path=%s", temp_path.c_str()); return false; } - const size_t written = std::fwrite(data, 1, len, file); - const int close_result = std::fclose(file); - if (written != len || close_result != 0) + const size_t written = file.write(data, len); + const bool flushed = file.flush(); + file.close(); + if (written != len || !flushed) { - std::remove(temp_path.c_str()); - ESP_LOGW(kIdfStoreTag, "save write failed path=%s want=%u got=%u close=%d", + (void)platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); + ESP_LOGW(kIdfStoreTag, "save write failed path=%s want=%u got=%u flush=%u", temp_path.c_str(), static_cast(len), static_cast(written), - close_result); + flushed ? 1U : 0U); return false; } - std::remove(path.c_str()); - if (std::rename(temp_path.c_str(), path.c_str()) != 0) + (void)platform::esp::arduino_common::storage::sd_remove(path.c_str()); + if (!platform::esp::arduino_common::storage::sd_rename(temp_path.c_str(), path.c_str())) { - std::remove(temp_path.c_str()); + (void)platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); ESP_LOGW(kIdfStoreTag, "save rename failed tmp=%s path=%s", temp_path.c_str(), path.c_str()); @@ -544,15 +542,18 @@ class IdfSdNodeBlobStore final : public chat::contacts::INodeBlobStore std::memcpy(file.data(), &header, sizeof(header)); std::memcpy(file.data() + sizeof(header), data, len); const bool sd_ok = writeSdFileAtomic(kIdfNodesFile, file.data(), file.size()); + const bool nvs_attempted = !sd_ok; const bool nvs_ok = - saveNvsBlob(kIdfNodesNvsKey, "node", data, len, kIdfMaxNodeFileBytes); + nvs_attempted + ? saveNvsBlob(kIdfNodesNvsKey, "node", data, len, kIdfMaxNodeFileBytes) + : false; ESP_LOGI(kIdfStoreTag, - "node save path=%s count=%u len=%u sd=%u nvs=%u", + "node save path=%s count=%u len=%u sd=%u nvs=%s", kIdfNodesFile, static_cast(header.count), static_cast(len), sd_ok ? 1U : 0U, - nvs_ok ? 1U : 0U); + nvs_attempted ? (nvs_ok ? "ok" : "fail") : "skipped"); return sd_ok || nvs_ok; } @@ -678,17 +679,9 @@ class IdfSdMeshPeerDirectoryBlobStore final } const std::string path = makeSdPath(kIdfMeshPeersFile); - struct stat info = {}; - if (::stat(path.c_str(), &info) != 0) + if (!platform::esp::arduino_common::storage::sd_exists(path.c_str())) { - return errno == ENOENT - ? chat::MeshPeerDirectoryBlobLoadResult::Missing - : chat::MeshPeerDirectoryBlobLoadResult::IoError; - } - if (!S_ISREG(info.st_mode) || info.st_size <= 0 || - static_cast(info.st_size) > kIdfMaxMeshPeerBlobBytes) - { - return chat::MeshPeerDirectoryBlobLoadResult::IoError; + return chat::MeshPeerDirectoryBlobLoadResult::Missing; } if (!readSdFile(kIdfMeshPeersFile, out, kIdfMaxMeshPeerBlobBytes)) { @@ -732,16 +725,8 @@ class IdfSdMeshPeerDirectoryBlobStore final } const std::string path = makeSdPath(kIdfMeshPeersDir); - struct stat info = {}; - if (::stat(path.c_str(), &info) == 0) - { - return S_ISDIR(info.st_mode); - } - if (::mkdir(path.c_str(), 0775) == 0) - { - return true; - } - return ::stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode); + return platform::esp::arduino_common::storage::sd_is_directory(path.c_str()) || + platform::esp::arduino_common::storage::sd_mkdir(path.c_str()); } }; diff --git a/apps/linux_sim_shell/CMakeLists.txt b/apps/linux_sim_shell/CMakeLists.txt index 7090d176..6c47b629 100644 --- a/apps/linux_sim_shell/CMakeLists.txt +++ b/apps/linux_sim_shell/CMakeLists.txt @@ -102,6 +102,8 @@ if(BUILD_TESTING) "${TRAIL_MATE_REPO_ROOT}/modules/ui_shared/tests/test_chat_presentation_source.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/domain/chat_model.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/usecase/chat_service.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_message_ledger.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/mesh_protocol_utils.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/store/ram_store.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_delivery_read_model.cpp" @@ -156,6 +158,8 @@ if(BUILD_TESTING) "${TRAIL_MATE_REPO_ROOT}/platform/linux/common/src/platform/linux/runtime_paths.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/domain/chat_model.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/usecase/chat_service.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_message_ledger.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/mesh_protocol_utils.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/store/ram_store.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_sys/src/sys/clock.cpp") @@ -176,6 +180,8 @@ if(BUILD_TESTING) "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/tests/test_chat_service_resend.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/domain/chat_model.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/usecase/chat_service.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_message_ledger.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/mesh_protocol_utils.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/store/ram_store.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_sys/src/sys/clock.cpp") @@ -212,11 +218,38 @@ if(BUILD_TESTING) add_test(NAME trailmate_chat_delivery_send_result_projection_smoke COMMAND trailmate_chat_delivery_send_result_projection_smoke) + add_executable(trailmate_chat_outbox_service_smoke + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/tests/test_chat_outbox_service.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp") + target_include_directories(trailmate_chat_outbox_service_smoke + PRIVATE + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/include") + target_compile_features(trailmate_chat_outbox_service_smoke + PRIVATE cxx_std_17) + add_test(NAME trailmate_chat_outbox_service_smoke + COMMAND trailmate_chat_outbox_service_smoke) + + add_executable(trailmate_chat_message_ledger_smoke + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/tests/test_chat_message_ledger.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/domain/chat_model.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/store/ram_store.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_message_ledger.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp") + target_include_directories(trailmate_chat_message_ledger_smoke + PRIVATE + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/include") + target_compile_features(trailmate_chat_message_ledger_smoke + PRIVATE cxx_std_17) + add_test(NAME trailmate_chat_message_ledger_smoke + COMMAND trailmate_chat_message_ledger_smoke) + add_executable(trailmate_chat_delivery_event_projection_adapter_smoke "${TRAIL_MATE_REPO_ROOT}/modules/ui_chat_runtime/tests/test_chat_delivery_event_projection_adapter.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/ui_chat_runtime/src/chat_delivery_event_projection_adapter.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/domain/chat_model.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/usecase/chat_service.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_message_ledger.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/mesh_protocol_utils.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/store/ram_store.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_delivery_read_model.cpp" @@ -413,8 +446,16 @@ if(BUILD_TESTING) "${TRAIL_MATE_REPO_ROOT}/tests/reticulum_conformance/test_reticulum_runtime_state_contract.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/lxmf/lxmf_wire.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/reticulum/reticulum_wire.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_destination_registry.cpp" "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_delivery_runtime.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_manager.cpp" "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_runtime.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_lxst_telephony_client.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_network_page_client.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_packet_router.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_path_manager.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_ping_service.cpp" + "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_client.cpp" "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_runtime.cpp" "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_service_runtime.cpp" "${TRAIL_MATE_REPO_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_resource_runtime.cpp" @@ -800,6 +841,8 @@ if(BUILD_TESTING) "${TRAIL_MATE_REPO_ROOT}/modules/core_team/tests/test_team_app_data_poll_order.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/domain/chat_model.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/usecase/chat_service.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_message_ledger.cpp" + "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_chat/src/infra/mesh_protocol_utils.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_team/src/protocol/team_chat.cpp" "${TRAIL_MATE_REPO_ROOT}/modules/core_team/src/protocol/team_wire.cpp" diff --git a/boards/t_display_p4/src/t_display_p4_board.cpp b/boards/t_display_p4/src/t_display_p4_board.cpp index a35dadbc..5865508c 100644 --- a/boards/t_display_p4/src/t_display_p4_board.cpp +++ b/boards/t_display_p4/src/t_display_p4_board.cpp @@ -14,9 +14,9 @@ #include "esp_err.h" #include "esp_log.h" #include "esp_sleep.h" -#include "esp_vfs_fat.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "platform/esp/idf_common/gps_runtime.h" +#include "platform/esp/idf_common/sd_card_runtime_sdfat_adapter.h" #include "platform/esp/idf_common/sdmmc_host_runtime.h" #include "platform/esp/idf_common/sx126x_radio.h" #include "platform/esp/idf_common/wireless_companion/c6_companion.h" @@ -1226,24 +1226,19 @@ bool TDisplayP4Board::mountSdCard(const char* mount_point, size_t max_files) slot_config.d3 = static_cast(sdmmcPins().d3); slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; - esp_vfs_fat_sdmmc_mount_config_t mount_config{}; - mount_config.format_if_mount_failed = false; - mount_config.max_files = max_files; - mount_config.allocation_unit_size = 16 * 1024; - - const esp_err_t err = platform::esp::idf_common::sdmmc_host_runtime::mount_fatfs( + const bool mounted = platform::esp::idf_common::sd_card_runtime::mount_sdmmc( platform::esp::idf_common::sdmmc_host_runtime::SlotOwner::SdCard, + host, + slot_config, mount_point, - &host, - &slot_config, - &mount_config, - &sd_card_); - if (err != ESP_OK) + static_cast(std::min(max_files, 255))); + if (!mounted) { - ESP_LOGW(kTag, "SD mount failed: %s", esp_err_to_name(err)); + ESP_LOGW(kTag, "SD mount failed via SdFat SDMMC backend"); return false; } + sd_card_ = platform::esp::idf_common::sd_card_runtime::mounted_card(); sd_ready_ = true; std::snprintf(sd_mount_point_, sizeof(sd_mount_point_), "%s", mount_point); if (sd_card_ != nullptr) @@ -1265,21 +1260,9 @@ bool TDisplayP4Board::unmountSdCard() { return true; } - if (!sd_card_ || sd_mount_point_[0] == '\0') - { - ESP_LOGE(kTag, "SD state is inconsistent during unmount"); - return false; - } - const esp_err_t err = platform::esp::idf_common::sdmmc_host_runtime::unmount_fatfs( - platform::esp::idf_common::sdmmc_host_runtime::SlotOwner::SdCard, - sd_mount_point_, - sd_card_); - if (err != ESP_OK) - { - ESP_LOGW(kTag, "SD unmount failed: %s", esp_err_to_name(err)); - return false; - } + platform::esp::idf_common::sd_card_runtime::unmount_sdmmc( + platform::esp::idf_common::sdmmc_host_runtime::SlotOwner::SdCard); sd_card_ = nullptr; sd_ready_ = false; diff --git a/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake b/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake index b78389d9..c1f80590 100644 --- a/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake +++ b/builds/esp_idf/ESP_IDF_COMPONENT_SOURCES.cmake @@ -61,6 +61,8 @@ set(TRAILMATE_ESP_IDF_CORE_CHAT_SOURCES "${TRAILMATE_ROOT}/modules/core_chat/src/delivery/chat_delivery_read_model.cpp" "${TRAILMATE_ROOT}/modules/core_chat/src/delivery/chat_delivery_message_projection.cpp" "${TRAILMATE_ROOT}/modules/core_chat/src/delivery/chat_delivery_send_result_projection.cpp" + "${TRAILMATE_ROOT}/modules/core_chat/src/delivery/chat_message_ledger.cpp" + "${TRAILMATE_ROOT}/modules/core_chat/src/delivery/chat_outbox_service.cpp" "${TRAILMATE_ROOT}/modules/core_chat/src/infra/contact_store_core.cpp" "${TRAILMATE_ROOT}/modules/core_chat/src/infra/mesh_adapter_router_core.cpp" "${TRAILMATE_ROOT}/modules/core_chat/src/infra/mesh_protocol_utils.cpp" @@ -396,7 +398,7 @@ set(TRAILMATE_ESP_IDF_PLATFORM_COMMON_SOURCES "${TRAILMATE_ROOT}/platform/esp/idf_common/src/bsp_runtime.cpp" "${TRAILMATE_ROOT}/platform/esp/idf_common/src/c6_companion_runtime.cpp" "${TRAILMATE_ROOT}/platform/esp/idf_common/src/flash_storage_runtime.cpp" - "${TRAILMATE_ROOT}/platform/esp/idf_common/src/sd_card_runtime_ffat_adapter.cpp" + "${TRAILMATE_ROOT}/platform/esp/idf_common/src/sd_card_runtime_sdfat_adapter.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/store/sd_store.cpp" "${TRAILMATE_ROOT}/platform/esp/idf_common/src/chat_blob_store_io.cpp" "${TRAILMATE_ROOT}/platform/esp/idf_common/src/debug/sd_coredump_export.cpp" @@ -444,9 +446,18 @@ set(TRAILMATE_ESP_IDF_PLATFORM_COMMON_SOURCES "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_identity.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter_call.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_announce_ingestor.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_delivery_runtime.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_destination_registry.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_identity.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_manager.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_runtime.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_lxst_telephony_client.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_network_page_client.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_packet_router.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_path_manager.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_ping_service.cpp" + "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_client.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_runtime.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_stamp_runtime.cpp" "${TRAILMATE_ROOT}/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_service_runtime.cpp" @@ -472,6 +483,32 @@ set(TRAILMATE_ESP_IDF_PLATFORM_COMMON_SOURCES "${TRAILMATE_ROOT}/platform/esp/radio/meshtastic_radio_adapter.cpp") list(APPEND TRAILMATE_ESP_IDF_PLATFORM_COMMON_SOURCES + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/FmtNumber.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/FsCache.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/FsDateTime.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/FsName.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/FsStructs.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/FsUtf.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/PrintBasic.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/common/upcase.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/ExFatLib/ExFatDbg.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/ExFatLib/ExFatFile.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/ExFatLib/ExFatFilePrint.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/ExFatLib/ExFatFileWrite.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/ExFatLib/ExFatName.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/ExFatLib/ExFatPartition.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/ExFatLib/ExFatVolume.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatDbg.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatFile.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatFileLFN.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatFilePrint.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatFileSFN.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatName.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatPartition.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FatLib/FatVolume.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FsLib/FsFile.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FsLib/FsNew.cpp" + "${TRAILMATE_ROOT}/third_party/sdfat/src/FsLib/FsVolume.cpp" ${trail_mate_codec2_sources} "${TRAILMATE_ROOT}/third_party/bzip2/src/bz_internal_error.c" "${TRAILMATE_ROOT}/third_party/bzip2/src/bzlib.c" @@ -503,6 +540,7 @@ set(TRAILMATE_ESP_IDF_FINAL_INCLUDE_DIRS "${TRAILMATE_ROOT}/modules/core_chat/third_party/nanopb" "${TRAILMATE_ROOT}/third_party/bzip2/src" "${TRAILMATE_ROOT}/third_party/codec2/src" + "${TRAILMATE_ROOT}/third_party/sdfat/src" "${TRAILMATE_ROOT}/modules/core_device/include" "${TRAILMATE_ROOT}/modules/core_gps/include" "${TRAILMATE_ROOT}/modules/core_mesh/include" diff --git a/builds/esp_idf/main/CMakeLists.txt b/builds/esp_idf/main/CMakeLists.txt index 488db5ac..59ebf24c 100644 --- a/builds/esp_idf/main/CMakeLists.txt +++ b/builds/esp_idf/main/CMakeLists.txt @@ -51,7 +51,13 @@ idf_component_register( ) target_compile_definitions(${COMPONENT_LIB} PRIVATE - TRAIL_MATE_LORA_TX_POWER_MAX_DBM=22) + TRAIL_MATE_LORA_TX_POWER_MAX_DBM=22 + SDFAT_FILE_TYPE=3 + USE_BLOCK_DEVICE_INTERFACE=1 + ENABLE_ARDUINO_FEATURES=0 + ENABLE_ARDUINO_SERIAL=0 + ENABLE_ARDUINO_STRING=0 + USE_FCNTL_H=1) if(TRAIL_MATE_IDF_TARGET STREQUAL "tab5") target_compile_definitions(${COMPONENT_LIB} PRIVATE TRAIL_MATE_ESP_BOARD_TAB5=1) diff --git a/modules/core_chat/include/chat/delivery/chat_message_ledger.h b/modules/core_chat/include/chat/delivery/chat_message_ledger.h new file mode 100644 index 00000000..c787b257 --- /dev/null +++ b/modules/core_chat/include/chat/delivery/chat_message_ledger.h @@ -0,0 +1,30 @@ +#pragma once + +#include "chat/domain/chat_model.h" +#include "chat/ports/i_chat_store.h" + +namespace chat::delivery +{ + +class ChatMessageLedger final +{ + public: + ChatMessageLedger(ChatModel& model, IChatStore& store); + + void recordOutbound(const ChatMessage& message, bool model_enabled); + bool applyOutboundStatus(MessageId msg_id, + MessageStatus status, + bool model_enabled); + bool markRetryQueued(MessageId msg_id, bool model_enabled); + + private: + bool lookupMessage(MessageId msg_id, ChatMessage& out) const; + bool writeStatus(MessageId msg_id, + MessageStatus status, + bool model_enabled); + + ChatModel& model_; + IChatStore& store_; +}; + +} // namespace chat::delivery diff --git a/modules/core_chat/include/chat/delivery/chat_outbox_service.h b/modules/core_chat/include/chat/delivery/chat_outbox_service.h new file mode 100644 index 00000000..e0c5fde1 --- /dev/null +++ b/modules/core_chat/include/chat/delivery/chat_outbox_service.h @@ -0,0 +1,19 @@ +#pragma once + +#include "chat/delivery/chat_delivery_event_projector.h" +#include "chat/domain/chat_types.h" + +namespace chat::delivery +{ + +class ChatOutboxService final +{ + public: + static bool isOutboundStatusUpdate(chat::MessageStatus status); + static bool shouldApplyStatus(const chat::ChatMessage* current, + chat::MessageStatus next); + static DeliveryState toDeliveryState(chat::MessageStatus status); + static SendFailureKind failureForStatus(chat::MessageStatus status); +}; + +} // namespace chat::delivery diff --git a/modules/core_chat/include/chat/infra/lxmf/lxmf_wire.h b/modules/core_chat/include/chat/infra/lxmf/lxmf_wire.h index 972e0f2f..f0826ceb 100644 --- a/modules/core_chat/include/chat/infra/lxmf/lxmf_wire.h +++ b/modules/core_chat/include/chat/infra/lxmf/lxmf_wire.h @@ -31,6 +31,16 @@ struct ByteSpan size_t size = 0; }; +struct ByteSpanList +{ + const ByteSpan* items = nullptr; + size_t size = 0; +}; + +using BinItemCallback = bool (*)(const uint8_t* data, + size_t len, + void* context); + struct DecodedEnvelope { uint8_t destination_hash[reticulum::kTruncatedHashSize] = {}; @@ -130,6 +140,12 @@ struct DecodedPropagationBatch std::vector> messages; }; +struct DecodedPropagationOfferHeader +{ + bool peering_key_is_nil = true; + ByteSpan peering_key; +}; + struct DecodedPropagationOffer { bool peering_key_is_nil = true; @@ -137,6 +153,14 @@ struct DecodedPropagationOffer std::vector> transient_ids; }; +struct DecodedPropagationGetRequestHeader +{ + bool wants_is_nil = true; + bool haves_is_nil = true; + bool has_transfer_limit = false; + uint32_t transfer_limit_kb = 0; +}; + struct DecodedPropagationGetRequest { bool wants_is_nil = true; @@ -251,15 +275,37 @@ bool encodePropagationBatch(double remote_timebase, const std::vector& messages, uint8_t* out_payload, size_t* inout_len); +bool encodePropagationBatch(double remote_timebase, + ByteSpanList messages, + uint8_t* out_payload, + size_t* inout_len); bool decodePropagationBatch(const uint8_t* data, size_t len, DecodedPropagationBatch* out_batch); +bool decodePropagationBatch(const uint8_t* data, + size_t len, + BinItemCallback on_message, + void* callback_context, + double* out_remote_timebase); bool decodePropagationOfferPayload(const uint8_t* data, size_t len, DecodedPropagationOffer* out_offer); +bool decodePropagationOfferPayload(const uint8_t* data, + size_t len, + BinItemCallback on_transient_id, + void* callback_context, + DecodedPropagationOfferHeader* out_offer); bool decodePropagationGetRequestPayload(const uint8_t* data, size_t len, DecodedPropagationGetRequest* out_request); +bool decodePropagationGetRequestPayload( + const uint8_t* data, + size_t len, + BinItemCallback on_want, + void* want_context, + BinItemCallback on_have, + void* have_context, + DecodedPropagationGetRequestHeader* out_request); bool encodePropagationGetRequestPayload( const std::vector>* wants, @@ -268,15 +314,29 @@ bool encodePropagationGetRequestPayload( uint32_t transfer_limit_kb, uint8_t* out_payload, size_t* inout_len); +bool encodePropagationGetRequestPayloadSpans(const ByteSpanList* wants, + const ByteSpanList* haves, + bool include_transfer_limit, + uint32_t transfer_limit_kb, + uint8_t* out_payload, + size_t* inout_len); bool decodePropagationIdListPayload(const uint8_t* data, size_t len, std::vector>* out_ids); +bool decodePropagationIdListPayload(const uint8_t* data, + size_t len, + BinItemCallback on_item, + void* callback_context); bool decodePropagationMessageListPayload( const uint8_t* data, size_t len, std::vector>* out_messages); +bool decodePropagationMessageListPayload(const uint8_t* data, + size_t len, + BinItemCallback on_message, + void* callback_context); bool decodePropagationAnnounceAppData( const uint8_t* data, @@ -286,6 +346,9 @@ bool decodePropagationAnnounceAppData( bool encodePropagationIdListPayload(const std::vector>& ids, uint8_t* out_payload, size_t* inout_len); +bool encodePropagationIdListPayload(ByteSpanList ids, + uint8_t* out_payload, + size_t* inout_len); bool encodePropagationMessageListPayload(const std::vector>& messages, uint8_t* out_payload, @@ -293,6 +356,9 @@ bool encodePropagationMessageListPayload(const std::vector> bool encodePropagationMessageListPayload(const std::vector& messages, uint8_t* out_payload, size_t* inout_len); +bool encodePropagationMessageListPayload(ByteSpanList messages, + uint8_t* out_payload, + size_t* inout_len); void computeMessageHash(const uint8_t destination_hash[reticulum::kTruncatedHashSize], const uint8_t source_hash[reticulum::kTruncatedHashSize], diff --git a/modules/core_chat/include/chat/usecase/chat_service.h b/modules/core_chat/include/chat/usecase/chat_service.h index e6869e6f..77134706 100644 --- a/modules/core_chat/include/chat/usecase/chat_service.h +++ b/modules/core_chat/include/chat/usecase/chat_service.h @@ -9,6 +9,7 @@ #include "../domain/chat_types.h" #include "../ports/i_chat_store.h" #include "../ports/i_mesh_adapter.h" +#include "chat/delivery/chat_message_ledger.h" #include #include #include @@ -160,9 +161,9 @@ class ChatService void handleSendResult(MessageId msg_id, bool ok); /** - * @brief Apply an exact outbound delivery state. + * @brief Apply an outbound delivery state update. * - * Only Sent, Delivered, and Failed are accepted. Delivered is terminal; + * Queued, Sent, Delivered, and Failed are accepted. Delivered is terminal; * an earlier failure may still be superseded by a later valid proof. */ void handleSendResult(MessageId msg_id, MessageStatus status); @@ -254,6 +255,7 @@ class ChatService ChatModel& model_; IMeshAdapter& adapter_; IChatStore& store_; + delivery::ChatMessageLedger message_ledger_; ChannelId current_channel_; bool model_enabled_ = true; MeshProtocol active_protocol_ = MeshProtocol::Meshtastic; diff --git a/modules/core_chat/src/delivery/chat_delivery_send_result_projection.cpp b/modules/core_chat/src/delivery/chat_delivery_send_result_projection.cpp index 32828121..36dbb954 100644 --- a/modules/core_chat/src/delivery/chat_delivery_send_result_projection.cpp +++ b/modules/core_chat/src/delivery/chat_delivery_send_result_projection.cpp @@ -23,7 +23,8 @@ ChatDeliveryEvent makeChatSendResultDeliveryEvent(ChatDeliveryRef ref, ChatDeliveryEvent event{}; event.ref = ref; event.timestamp_ms = timestamp_ms; - if (state == DeliveryState::Sent || state == DeliveryState::Delivered) + if (state == DeliveryState::Queued || state == DeliveryState::Sending || + state == DeliveryState::Sent || state == DeliveryState::Delivered) { event.state = state; event.failure = SendFailureKind::None; diff --git a/modules/core_chat/src/delivery/chat_message_ledger.cpp b/modules/core_chat/src/delivery/chat_message_ledger.cpp new file mode 100644 index 00000000..6caa0106 --- /dev/null +++ b/modules/core_chat/src/delivery/chat_message_ledger.cpp @@ -0,0 +1,87 @@ +#include "chat/delivery/chat_message_ledger.h" + +#include "chat/delivery/chat_outbox_service.h" + +namespace chat::delivery +{ + +ChatMessageLedger::ChatMessageLedger(ChatModel& model, IChatStore& store) + : model_(model), store_(store) +{ +} + +void ChatMessageLedger::recordOutbound(const ChatMessage& message, + bool model_enabled) +{ + if (model_enabled) + { + model_.onSendQueued(message); + if (message.status == MessageStatus::Failed && message.msg_id != 0) + { + model_.onSendResult(message.msg_id, false); + } + } + store_.append(message); +} + +bool ChatMessageLedger::applyOutboundStatus(MessageId msg_id, + MessageStatus status, + bool model_enabled) +{ + ChatMessage current{}; + if (!lookupMessage(msg_id, current)) + { + return false; + } + if (!ChatOutboxService::shouldApplyStatus(¤t, status)) + { + return false; + } + return writeStatus(msg_id, status, model_enabled); +} + +bool ChatMessageLedger::markRetryQueued(MessageId msg_id, bool model_enabled) +{ + ChatMessage current{}; + if (!lookupMessage(msg_id, current)) + { + return false; + } + if (current.from != 0 || current.status != MessageStatus::Failed) + { + return false; + } + return writeStatus(msg_id, MessageStatus::Queued, model_enabled); +} + +bool ChatMessageLedger::lookupMessage(MessageId msg_id, ChatMessage& out) const +{ + if (msg_id == 0) + { + return false; + } + if (const ChatMessage* message = model_.getMessage(msg_id)) + { + out = *message; + return true; + } + return store_.getMessage(msg_id, &out); +} + +bool ChatMessageLedger::writeStatus(MessageId msg_id, + MessageStatus status, + bool model_enabled) +{ + if (msg_id == 0) + { + return false; + } + bool updated = false; + if (model_enabled) + { + updated = model_.updateMessageStatus(msg_id, status); + } + return store_.updateMessageStatus(msg_id, status) || updated; +} + +} // namespace chat::delivery diff --git a/modules/core_chat/src/delivery/chat_outbox_service.cpp b/modules/core_chat/src/delivery/chat_outbox_service.cpp new file mode 100644 index 00000000..83a203b5 --- /dev/null +++ b/modules/core_chat/src/delivery/chat_outbox_service.cpp @@ -0,0 +1,84 @@ +#include "chat/delivery/chat_outbox_service.h" + +namespace chat::delivery +{ + +bool ChatOutboxService::isOutboundStatusUpdate(chat::MessageStatus status) +{ + switch (status) + { + case chat::MessageStatus::Queued: + case chat::MessageStatus::Sent: + case chat::MessageStatus::Delivered: + case chat::MessageStatus::Failed: + return true; + case chat::MessageStatus::Incoming: + return false; + } + return false; +} + +bool ChatOutboxService::shouldApplyStatus(const chat::ChatMessage* current, + chat::MessageStatus next) +{ + if (!isOutboundStatusUpdate(next)) + { + return false; + } + if (current == nullptr) + { + return true; + } + if (current->from != 0) + { + return false; + } + if (current->status == next) + { + return true; + } + if (current->status == chat::MessageStatus::Delivered) + { + return false; + } + if (current->status == chat::MessageStatus::Sent && + (next == chat::MessageStatus::Queued || + next == chat::MessageStatus::Failed)) + { + return false; + } + if (current->status == chat::MessageStatus::Failed && + (next == chat::MessageStatus::Queued || + next == chat::MessageStatus::Sent)) + { + return false; + } + return true; +} + +DeliveryState ChatOutboxService::toDeliveryState(chat::MessageStatus status) +{ + switch (status) + { + case chat::MessageStatus::Queued: + return DeliveryState::Queued; + case chat::MessageStatus::Sent: + return DeliveryState::Sent; + case chat::MessageStatus::Delivered: + return DeliveryState::Delivered; + case chat::MessageStatus::Failed: + return DeliveryState::Failed; + case chat::MessageStatus::Incoming: + return DeliveryState::Received; + } + return DeliveryState::Unknown; +} + +SendFailureKind ChatOutboxService::failureForStatus( + chat::MessageStatus status) +{ + return status == chat::MessageStatus::Failed ? SendFailureKind::Unknown + : SendFailureKind::None; +} + +} // namespace chat::delivery diff --git a/modules/core_chat/src/infra/lxmf/lxmf_wire.cpp b/modules/core_chat/src/infra/lxmf/lxmf_wire.cpp index c5641602..e4dfeca4 100644 --- a/modules/core_chat/src/infra/lxmf/lxmf_wire.cpp +++ b/modules/core_chat/src/infra/lxmf/lxmf_wire.cpp @@ -392,12 +392,14 @@ uint32_t readU32Be(const uint8_t* data) static_cast(data[3]); } -bool readBinary(Cursor& cursor, std::vector* out_data) +bool readBinarySpan(Cursor& cursor, const uint8_t** out_data, size_t* out_len) { - if (!out_data) + if (!out_data || !out_len) { return false; } + *out_data = nullptr; + *out_len = 0; uint8_t tag = 0; if (!readByte(cursor, &tag)) @@ -458,11 +460,30 @@ bool readBinary(Cursor& cursor, std::vector* out_data) return false; } - out_data->assign(cursor.data + cursor.pos, cursor.data + cursor.pos + len); + *out_data = cursor.data + cursor.pos; + *out_len = len; cursor.pos += len; return true; } +bool readBinary(Cursor& cursor, std::vector* out_data) +{ + if (!out_data) + { + return false; + } + + const uint8_t* data = nullptr; + size_t len = 0; + if (!readBinarySpan(cursor, &data, &len)) + { + return false; + } + + out_data->assign(data, data + len); + return true; +} + bool skipObject(Cursor& cursor) { uint8_t tag = 0; @@ -569,22 +590,43 @@ bool appendArrayOfBins(const std::vector>& items, return true; } +bool appendArrayOfBinSpans(ByteSpanList items, + uint8_t* out, + size_t out_len, + size_t& used); + bool appendArrayOfBinSpans(const std::vector& items, uint8_t* out, size_t out_len, size_t& used) { - if (items.size() > kMaxPropagationWireItems) + return appendArrayOfBinSpans(ByteSpanList{items.data(), items.size()}, + out, + out_len, + used); +} + +bool appendArrayOfBinSpans(ByteSpanList items, + uint8_t* out, + size_t out_len, + size_t& used) +{ + if (items.size > kMaxPropagationWireItems) { return false; } - if (!appendArrayHeader(static_cast(items.size()), out, out_len, used)) + if (items.size != 0U && !items.items) + { + return false; + } + if (!appendArrayHeader(static_cast(items.size), out, out_len, used)) { return false; } - for (const auto& item : items) + for (size_t index = 0; index < items.size; ++index) { + const ByteSpan& item = items.items[index]; if ((!item.data && item.size != 0U) || !appendBin(item.data, item.size, out, out_len, used)) { @@ -629,6 +671,40 @@ bool readArrayOfBins(Cursor& cursor, std::vector>* out_item return true; } +bool readArrayOfBins(Cursor& cursor, + BinItemCallback on_item, + void* callback_context) +{ + if (!on_item) + { + return false; + } + + size_t count = 0; + if (!readArrayHeader(cursor, &count)) + { + return false; + } + if (count > kMaxPropagationWireItems || + count > cursor.len - cursor.pos) + { + return false; + } + + for (size_t i = 0; i < count; ++i) + { + const uint8_t* item_data = nullptr; + size_t item_len = 0; + if (!readBinarySpan(cursor, &item_data, &item_len) || + !on_item(item_data, item_len, callback_context)) + { + return false; + } + } + + return true; +} + } // namespace bool packPeerAnnounceAppData(const char* display_name, @@ -1495,6 +1571,17 @@ bool encodePropagationBatch(double remote_timebase, const std::vector& messages, uint8_t* out_payload, size_t* inout_len) +{ + return encodePropagationBatch(remote_timebase, + ByteSpanList{messages.data(), messages.size()}, + out_payload, + inout_len); +} + +bool encodePropagationBatch(double remote_timebase, + ByteSpanList messages, + uint8_t* out_payload, + size_t* inout_len) { if (!out_payload || !inout_len) { @@ -1544,6 +1631,36 @@ bool decodePropagationBatch(const uint8_t* data, size_t len, return true; } +bool decodePropagationBatch(const uint8_t* data, + size_t len, + BinItemCallback on_message, + void* callback_context, + double* out_remote_timebase) +{ + if (!data || len == 0 || !on_message || !out_remote_timebase) + { + return false; + } + + Cursor cursor; + cursor.data = data; + cursor.len = len; + cursor.pos = 0; + + size_t count = 0; + double remote_timebase = 0.0; + if (!readArrayHeader(cursor, &count) || count != 2 || + !readFloat64(cursor, &remote_timebase) || + !readArrayOfBins(cursor, on_message, callback_context) || + cursor.pos != cursor.len) + { + return false; + } + + *out_remote_timebase = remote_timebase; + return true; +} + bool decodePropagationOfferPayload(const uint8_t* data, size_t len, DecodedPropagationOffer* out_offer) { @@ -1605,6 +1722,73 @@ bool decodePropagationOfferPayload(const uint8_t* data, size_t len, return true; } +bool decodePropagationOfferPayload(const uint8_t* data, + size_t len, + BinItemCallback on_transient_id, + void* callback_context, + DecodedPropagationOfferHeader* out_offer) +{ + if (!data || len == 0 || !on_transient_id || !out_offer) + { + return false; + } + + Cursor cursor; + cursor.data = data; + cursor.len = len; + cursor.pos = 0; + + size_t count = 0; + if (!readArrayHeader(cursor, &count) || count < 2) + { + return false; + } + + DecodedPropagationOfferHeader decoded{}; + uint8_t next = 0; + if (!peekByte(cursor, &next)) + { + return false; + } + if (next == 0xC0) + { + if (!readNil(cursor)) + { + return false; + } + } + else + { + const uint8_t* peering_key = nullptr; + size_t peering_key_len = 0; + if (!readBinarySpan(cursor, &peering_key, &peering_key_len)) + { + return false; + } + decoded.peering_key_is_nil = false; + decoded.peering_key = ByteSpan{peering_key, peering_key_len}; + } + + if (!readArrayOfBins(cursor, on_transient_id, callback_context)) + { + return false; + } + for (size_t index = 2; index < count; ++index) + { + if (!skipObject(cursor)) + { + return false; + } + } + if (cursor.pos != cursor.len) + { + return false; + } + + *out_offer = decoded; + return true; +} + bool decodePropagationGetRequestPayload(const uint8_t* data, size_t len, DecodedPropagationGetRequest* out_request) { @@ -1692,6 +1876,101 @@ bool decodePropagationGetRequestPayload(const uint8_t* data, size_t len, return true; } +bool decodePropagationGetRequestPayload( + const uint8_t* data, + size_t len, + BinItemCallback on_want, + void* want_context, + BinItemCallback on_have, + void* have_context, + DecodedPropagationGetRequestHeader* out_request) +{ + if (!data || len == 0 || !out_request) + { + return false; + } + + Cursor cursor; + cursor.data = data; + cursor.len = len; + cursor.pos = 0; + + size_t count = 0; + if (!readArrayHeader(cursor, &count) || count < 2) + { + return false; + } + + DecodedPropagationGetRequestHeader decoded{}; + uint8_t next = 0; + if (!peekByte(cursor, &next)) + { + return false; + } + if (next == 0xC0) + { + if (!readNil(cursor)) + { + return false; + } + } + else + { + if (!on_want || + !readArrayOfBins(cursor, on_want, want_context)) + { + return false; + } + decoded.wants_is_nil = false; + } + + if (!peekByte(cursor, &next)) + { + return false; + } + if (next == 0xC0) + { + if (!readNil(cursor)) + { + return false; + } + } + else + { + if (!on_have || + !readArrayOfBins(cursor, on_have, have_context)) + { + return false; + } + decoded.haves_is_nil = false; + } + + if (count >= 3) + { + uint32_t limit_kb = 0; + if (!readUint(cursor, &limit_kb)) + { + return false; + } + decoded.has_transfer_limit = true; + decoded.transfer_limit_kb = limit_kb; + } + for (size_t index = 3; index < count; ++index) + { + if (!skipObject(cursor)) + { + return false; + } + } + if (cursor.pos != cursor.len) + { + return false; + } + + *out_request = decoded; + return true; +} + bool encodePropagationGetRequestPayload( const std::vector>* wants, const std::vector>* haves, @@ -1745,6 +2024,58 @@ bool encodePropagationGetRequestPayload( return true; } +bool encodePropagationGetRequestPayloadSpans(const ByteSpanList* wants, + const ByteSpanList* haves, + bool include_transfer_limit, + uint32_t transfer_limit_kb, + uint8_t* out_payload, + size_t* inout_len) +{ + if (!out_payload || !inout_len) + { + return false; + } + + size_t used = 0; + if (!appendArrayHeader(include_transfer_limit ? 3 : 2, + out_payload, + *inout_len, + used)) + { + return false; + } + if (wants) + { + if (!appendArrayOfBinSpans(*wants, out_payload, *inout_len, used)) + { + return false; + } + } + else if (!appendNil(out_payload, *inout_len, used)) + { + return false; + } + if (haves) + { + if (!appendArrayOfBinSpans(*haves, out_payload, *inout_len, used)) + { + return false; + } + } + else if (!appendNil(out_payload, *inout_len, used)) + { + return false; + } + if (include_transfer_limit && + !appendUint(transfer_limit_kb, out_payload, *inout_len, used)) + { + return false; + } + + *inout_len = used; + return true; +} + bool decodePropagationIdListPayload( const uint8_t* data, size_t len, @@ -1764,6 +2095,24 @@ bool decodePropagationIdListPayload( return true; } +bool decodePropagationIdListPayload(const uint8_t* data, + size_t len, + BinItemCallback on_item, + void* callback_context) +{ + if (!data || len == 0 || !on_item) + { + return false; + } + Cursor cursor{data, len, 0}; + if (!readArrayOfBins(cursor, on_item, callback_context) || + cursor.pos != cursor.len) + { + return false; + } + return true; +} + bool decodePropagationMessageListPayload( const uint8_t* data, size_t len, @@ -1772,6 +2121,14 @@ bool decodePropagationMessageListPayload( return decodePropagationIdListPayload(data, len, out_messages); } +bool decodePropagationMessageListPayload(const uint8_t* data, + size_t len, + BinItemCallback on_message, + void* callback_context) +{ + return decodePropagationIdListPayload(data, len, on_message, callback_context); +} + bool decodePropagationAnnounceAppData( const uint8_t* data, size_t len, @@ -1879,6 +2236,25 @@ bool encodePropagationIdListPayload(const std::vector>& ids return true; } +bool encodePropagationIdListPayload(ByteSpanList ids, + uint8_t* out_payload, + size_t* inout_len) +{ + if (!out_payload || !inout_len) + { + return false; + } + + size_t used = 0; + if (!appendArrayOfBinSpans(ids, out_payload, *inout_len, used)) + { + return false; + } + + *inout_len = used; + return true; +} + bool encodePropagationMessageListPayload(const std::vector>& messages, uint8_t* out_payload, size_t* inout_len) @@ -1889,6 +2265,16 @@ bool encodePropagationMessageListPayload(const std::vector> bool encodePropagationMessageListPayload(const std::vector& messages, uint8_t* out_payload, size_t* inout_len) +{ + return encodePropagationMessageListPayload( + ByteSpanList{messages.data(), messages.size()}, + out_payload, + inout_len); +} + +bool encodePropagationMessageListPayload(ByteSpanList messages, + uint8_t* out_payload, + size_t* inout_len) { if (!out_payload || !inout_len) { diff --git a/modules/core_chat/src/usecase/chat_service.cpp b/modules/core_chat/src/usecase/chat_service.cpp index fb4dcbdc..0770bda8 100644 --- a/modules/core_chat/src/usecase/chat_service.cpp +++ b/modules/core_chat/src/usecase/chat_service.cpp @@ -164,6 +164,7 @@ ChatService::ChatService(ChatModel& model, IChatStore& store, MeshProtocol active_protocol) : model_(model), adapter_(adapter), store_(store), + message_ledger_(model, store), current_channel_(ChannelId::PRIMARY), active_protocol_(active_protocol) { @@ -320,16 +321,7 @@ MeshSendResult ChatService::sendTextResolvedDetailed( msg.reticulum_identity = result.reticulum_identity; msg.status = result.ok ? MessageStatus::Queued : MessageStatus::Failed; - if (model_enabled_) - { - model_.onSendQueued(msg); - if (!result.ok && result.msg_id != 0) - { - model_.onSendResult(result.msg_id, false); - } - } - - store_.append(msg); + message_ledger_.recordOutbound(msg, model_enabled_); CHAT_SERVICE_DIAG_LOG("[ChatService][TX] stored msg=%lu status=%u peer=%08lX dest=%s text=\"%s\"\n", static_cast(msg.msg_id), static_cast(msg.status), @@ -459,12 +451,7 @@ bool ChatService::resendFailed(MessageId msg_id) return false; } - if (model_enabled_) - { - model_.updateMessageStatus(msg.msg_id, MessageStatus::Queued); - } - store_.updateMessageStatus(msg.msg_id, MessageStatus::Queued); - return true; + return message_ledger_.markRetryQueued(msg.msg_id, model_enabled_); } std::vector ChatService::getRecentMessages(const ConversationId& conv, size_t limit) const @@ -837,43 +824,7 @@ void ChatService::handleSendResult(MessageId msg_id, bool ok) void ChatService::handleSendResult(MessageId msg_id, MessageStatus status) { - if (msg_id == 0) - { - return; - } - if (status != MessageStatus::Sent && - status != MessageStatus::Delivered && - status != MessageStatus::Failed) - { - return; - } - - const ChatMessage* current = getMessage(msg_id); - if (current && current->from != 0) - { - return; - } - if (current && current->status == MessageStatus::Delivered) - { - return; - } - if (current && current->status == MessageStatus::Sent && - status == MessageStatus::Failed) - { - CHAT_SERVICE_DIAG_LOG("[ChatService][TX] ignore failed result for sent msg=%lu\n", - static_cast(msg_id)); - return; - } - if (current && current->status == MessageStatus::Failed && - status == MessageStatus::Sent) - { - return; - } - if (model_enabled_) - { - model_.updateMessageStatus(msg_id, status); - } - store_.updateMessageStatus(msg_id, status); + (void)message_ledger_.applyOutboundStatus(msg_id, status, model_enabled_); } const ChatMessage* ChatService::getMessage(MessageId msg_id) const diff --git a/modules/core_chat/tests/test_chat_delivery_send_result_projection.cpp b/modules/core_chat/tests/test_chat_delivery_send_result_projection.cpp index 6b1a902d..73d7b47d 100644 --- a/modules/core_chat/tests/test_chat_delivery_send_result_projection.cpp +++ b/modules/core_chat/tests/test_chat_delivery_send_result_projection.cpp @@ -19,6 +19,22 @@ int main() assert(event.failure == SendFailureKind::None); assert(event.timestamp_ms == 111); + event = makeChatSendResultDeliveryEvent(ref, + DeliveryState::Queued, + SendFailureKind::RadioSendFailed, + 110); + assert(event.state == DeliveryState::Queued); + assert(event.failure == SendFailureKind::None); + assert(event.timestamp_ms == 110); + + event = makeChatSendResultDeliveryEvent(ref, + DeliveryState::Sending, + SendFailureKind::RadioSendFailed, + 111); + assert(event.state == DeliveryState::Sending); + assert(event.failure == SendFailureKind::None); + assert(event.timestamp_ms == 111); + event = makeChatSendResultDeliveryEvent(ref, DeliveryState::Delivered, SendFailureKind::RadioSendFailed, diff --git a/modules/core_chat/tests/test_chat_message_ledger.cpp b/modules/core_chat/tests/test_chat_message_ledger.cpp new file mode 100644 index 00000000..42c01968 --- /dev/null +++ b/modules/core_chat/tests/test_chat_message_ledger.cpp @@ -0,0 +1,91 @@ +#include "chat/delivery/chat_message_ledger.h" +#include "chat/infra/store/ram_store.h" + +#include + +namespace +{ + +chat::ChatMessage outgoing(chat::MessageId id, chat::MessageStatus status) +{ + chat::ChatMessage message; + message.protocol = chat::MeshProtocol::Meshtastic; + message.channel = chat::ChannelId::PRIMARY; + message.from = 0; + message.peer = 0xAABBCCDD; + message.msg_id = id; + message.text = "ledger"; + message.status = status; + return message; +} + +chat::ChatMessage incoming(chat::MessageId id) +{ + chat::ChatMessage message = outgoing(id, chat::MessageStatus::Incoming); + message.from = 0x11223344; + return message; +} + +} // namespace + +int main() +{ + chat::ChatModel model; + chat::RamStore store; + chat::delivery::ChatMessageLedger ledger(model, store); + + ledger.recordOutbound(outgoing(100, chat::MessageStatus::Queued), true); + const chat::ChatMessage* model_message = model.getMessage(100); + assert(model_message != nullptr); + assert(model_message->status == chat::MessageStatus::Queued); + + chat::ChatMessage stored{}; + assert(store.getMessage(100, &stored)); + assert(stored.status == chat::MessageStatus::Queued); + + assert(ledger.applyOutboundStatus(100, chat::MessageStatus::Sent, true)); + assert(model.getMessage(100)->status == chat::MessageStatus::Sent); + assert(store.getMessage(100, &stored)); + assert(stored.status == chat::MessageStatus::Sent); + + assert(!ledger.applyOutboundStatus(100, chat::MessageStatus::Queued, true)); + assert(model.getMessage(100)->status == chat::MessageStatus::Sent); + assert(!ledger.applyOutboundStatus(100, chat::MessageStatus::Failed, true)); + assert(model.getMessage(100)->status == chat::MessageStatus::Sent); + + assert(ledger.applyOutboundStatus(100, + chat::MessageStatus::Delivered, + true)); + assert(model.getMessage(100)->status == chat::MessageStatus::Delivered); + assert(!ledger.applyOutboundStatus(100, chat::MessageStatus::Failed, true)); + assert(model.getMessage(100)->status == chat::MessageStatus::Delivered); + + ledger.recordOutbound(outgoing(200, chat::MessageStatus::Failed), true); + assert(model.getMessage(200)->status == chat::MessageStatus::Failed); + assert(!ledger.applyOutboundStatus(200, chat::MessageStatus::Queued, true)); + assert(!ledger.applyOutboundStatus(200, chat::MessageStatus::Sent, true)); + assert(model.getMessage(200)->status == chat::MessageStatus::Failed); + assert(ledger.markRetryQueued(200, true)); + assert(model.getMessage(200)->status == chat::MessageStatus::Queued); + + model.onIncoming(incoming(300)); + store.append(incoming(300)); + assert(!ledger.applyOutboundStatus(300, chat::MessageStatus::Sent, true)); + assert(!ledger.markRetryQueued(300, true)); + + chat::ChatModel store_only_model; + chat::RamStore store_only_store; + chat::delivery::ChatMessageLedger store_only_ledger(store_only_model, + store_only_store); + store_only_ledger.recordOutbound( + outgoing(400, chat::MessageStatus::Queued), + false); + assert(store_only_model.getMessage(400) == nullptr); + assert(store_only_ledger.applyOutboundStatus(400, + chat::MessageStatus::Sent, + false)); + assert(store_only_store.getMessage(400, &stored)); + assert(stored.status == chat::MessageStatus::Sent); + + return 0; +} diff --git a/modules/core_chat/tests/test_chat_outbox_service.cpp b/modules/core_chat/tests/test_chat_outbox_service.cpp new file mode 100644 index 00000000..a608a28f --- /dev/null +++ b/modules/core_chat/tests/test_chat_outbox_service.cpp @@ -0,0 +1,91 @@ +#include "chat/delivery/chat_outbox_service.h" + +#include + +namespace +{ + +chat::ChatMessage outgoing(chat::MessageStatus status) +{ + chat::ChatMessage message; + message.from = 0; + message.status = status; + return message; +} + +chat::ChatMessage incoming() +{ + chat::ChatMessage message; + message.from = 1234; + message.status = chat::MessageStatus::Incoming; + return message; +} + +} // namespace + +int main() +{ + using chat::MessageStatus; + using chat::delivery::ChatOutboxService; + using chat::delivery::DeliveryState; + + assert(ChatOutboxService::isOutboundStatusUpdate(MessageStatus::Queued)); + assert(ChatOutboxService::isOutboundStatusUpdate(MessageStatus::Sent)); + assert(ChatOutboxService::isOutboundStatusUpdate( + MessageStatus::Delivered)); + assert(ChatOutboxService::isOutboundStatusUpdate(MessageStatus::Failed)); + assert(!ChatOutboxService::isOutboundStatusUpdate( + MessageStatus::Incoming)); + + chat::ChatMessage queued = outgoing(MessageStatus::Queued); + assert(ChatOutboxService::shouldApplyStatus(&queued, + MessageStatus::Queued)); + assert(ChatOutboxService::shouldApplyStatus(&queued, MessageStatus::Sent)); + assert(ChatOutboxService::shouldApplyStatus(&queued, + MessageStatus::Delivered)); + assert(ChatOutboxService::shouldApplyStatus(&queued, + MessageStatus::Failed)); + + chat::ChatMessage sent = outgoing(MessageStatus::Sent); + assert(ChatOutboxService::shouldApplyStatus(&sent, MessageStatus::Sent)); + assert(!ChatOutboxService::shouldApplyStatus(&sent, + MessageStatus::Queued)); + assert(!ChatOutboxService::shouldApplyStatus(&sent, + MessageStatus::Failed)); + assert(ChatOutboxService::shouldApplyStatus(&sent, + MessageStatus::Delivered)); + + chat::ChatMessage failed = outgoing(MessageStatus::Failed); + assert(ChatOutboxService::shouldApplyStatus(&failed, + MessageStatus::Failed)); + assert(!ChatOutboxService::shouldApplyStatus(&failed, + MessageStatus::Queued)); + assert(!ChatOutboxService::shouldApplyStatus(&failed, + MessageStatus::Sent)); + assert(ChatOutboxService::shouldApplyStatus(&failed, + MessageStatus::Delivered)); + + chat::ChatMessage delivered = outgoing(MessageStatus::Delivered); + assert(ChatOutboxService::shouldApplyStatus(&delivered, + MessageStatus::Delivered)); + assert(!ChatOutboxService::shouldApplyStatus(&delivered, + MessageStatus::Queued)); + assert(!ChatOutboxService::shouldApplyStatus(&delivered, + MessageStatus::Sent)); + assert(!ChatOutboxService::shouldApplyStatus(&delivered, + MessageStatus::Failed)); + + chat::ChatMessage rx = incoming(); + assert(!ChatOutboxService::shouldApplyStatus(&rx, MessageStatus::Sent)); + + assert(ChatOutboxService::toDeliveryState(MessageStatus::Queued) == + DeliveryState::Queued); + assert(ChatOutboxService::toDeliveryState(MessageStatus::Sent) == + DeliveryState::Sent); + assert(ChatOutboxService::toDeliveryState(MessageStatus::Delivered) == + DeliveryState::Delivered); + assert(ChatOutboxService::toDeliveryState(MessageStatus::Failed) == + DeliveryState::Failed); + + return 0; +} diff --git a/modules/core_chat/tests/test_chat_service_resend.cpp b/modules/core_chat/tests/test_chat_service_resend.cpp index 24139155..df43c2b4 100644 --- a/modules/core_chat/tests/test_chat_service_resend.cpp +++ b/modules/core_chat/tests/test_chat_service_resend.cpp @@ -421,10 +421,17 @@ int main() assert(group_msg->status == chat::MessageStatus::Queued); } + service.handleSendResult(42, chat::MessageStatus::Queued); + msg = onlyMessage(service, conv); + assert(msg->msg_id == 42); + assert(msg->status == chat::MessageStatus::Queued); service.handleSendResult(42, true); msg = onlyMessage(service, conv); assert(msg->msg_id == 42); assert(msg->status == chat::MessageStatus::Sent); + service.handleSendResult(42, chat::MessageStatus::Queued); + msg = onlyMessage(service, conv); + assert(msg->status == chat::MessageStatus::Sent); service.handleSendResult(42, false); msg = onlyMessage(service, conv); assert(msg->status == chat::MessageStatus::Sent); @@ -447,6 +454,11 @@ int main() assert(list.size() == 2); assert(list.back().msg_id == 77); assert(list.back().status == chat::MessageStatus::Failed); + service.handleSendResult(77, chat::MessageStatus::Queued); + service.handleSendResult(77, chat::MessageStatus::Sent); + assert(service.getMessage(77)->status == chat::MessageStatus::Failed); + service.handleSendResult(77, chat::MessageStatus::Delivered); + assert(service.getMessage(77)->status == chat::MessageStatus::Delivered); mesh.next_send_ok = false; mesh.next_msg_id = 88; diff --git a/modules/ui_chat_runtime/src/chat_delivery_event_projection_adapter.cpp b/modules/ui_chat_runtime/src/chat_delivery_event_projection_adapter.cpp index 8c79007d..0a1f8e01 100644 --- a/modules/ui_chat_runtime/src/chat_delivery_event_projection_adapter.cpp +++ b/modules/ui_chat_runtime/src/chat_delivery_event_projection_adapter.cpp @@ -2,6 +2,7 @@ #include "chat/delivery/chat_delivery_message_projection.h" #include "chat/delivery/chat_delivery_send_result_projection.h" +#include "chat/delivery/chat_outbox_service.h" #include "chat/usecase/chat_service.h" namespace ui_chat_runtime @@ -20,28 +21,21 @@ void ChatDeliveryEventProjectionAdapter::onChatSendResult( ::chat::MessageStatus status, uint32_t timestamp_ms) { - if (status != ::chat::MessageStatus::Sent && - status != ::chat::MessageStatus::Delivered && - status != ::chat::MessageStatus::Failed) + if (!::chat::delivery::ChatOutboxService::isOutboundStatusUpdate(status)) { return; } const ::chat::ChatMessage* message = chat_service_.getMessage(msg_id); - if (status == ::chat::MessageStatus::Failed && message != nullptr && - (message->status == ::chat::MessageStatus::Sent || - message->status == ::chat::MessageStatus::Delivered)) + if (!::chat::delivery::ChatOutboxService::shouldApplyStatus(message, + status)) { return; } - const auto state = status == ::chat::MessageStatus::Delivered - ? ::chat::delivery::DeliveryState::Delivered - : status == ::chat::MessageStatus::Sent - ? ::chat::delivery::DeliveryState::Sent - : ::chat::delivery::DeliveryState::Failed; - const auto failure = status != ::chat::MessageStatus::Failed - ? ::chat::delivery::SendFailureKind::None - : ::chat::delivery::SendFailureKind::Unknown; + const auto state = + ::chat::delivery::ChatOutboxService::toDeliveryState(status); + const auto failure = + ::chat::delivery::ChatOutboxService::failureForStatus(status); (void)publishSendResult(msg_id, state, failure, timestamp_ms); } diff --git a/modules/ui_chat_runtime/tests/test_chat_delivery_event_projection_adapter.cpp b/modules/ui_chat_runtime/tests/test_chat_delivery_event_projection_adapter.cpp index d1d5d6c9..9863fc6b 100644 --- a/modules/ui_chat_runtime/tests/test_chat_delivery_event_projection_adapter.cpp +++ b/modules/ui_chat_runtime/tests/test_chat_delivery_event_projection_adapter.cpp @@ -67,11 +67,20 @@ int main() service.sendText(::chat::ChannelId::PRIMARY, "queued", 0); assert(sent_id == 700); + projection_adapter.onChatSendResult( + sent_id, ::chat::MessageStatus::Queued, 1200); + + ::chat::delivery::ChatDeliveryRecord record{}; + assert(read_model.find(::chat::delivery::ChatDeliveryRef{0, sent_id, 0}, + record)); + assert(record.state == ::chat::delivery::DeliveryState::Queued); + assert(record.failure == ::chat::delivery::DeliveryFailureKind::None); + assert(record.updated_at_ms == 1200); + service.handleSendResult(sent_id, true); projection_adapter.onChatSendResult( sent_id, ::chat::MessageStatus::Sent, 1234); - ::chat::delivery::ChatDeliveryRecord record{}; assert(read_model.find(::chat::delivery::ChatDeliveryRef{0, sent_id, 0}, record)); assert(record.state == ::chat::delivery::DeliveryState::Sent); diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h index 93845613..02914530 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h @@ -10,7 +10,16 @@ #include "chat/infra/mesh_incoming_queue.h" #include "chat/ports/i_mesh_adapter.h" #include "chat/ports/i_mesh_peer_directory.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_announce_ingestor.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_destination_registry.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_identity.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_manager.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_lxst_telephony_client.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_network_page_client.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_packet_router.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_path_manager.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_ping_service.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_client.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_stamp_runtime.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_runtime_state.h" #include "platform/esp/arduino_common/chat/infra/reticulum/reticulum_interfaces.h" @@ -96,6 +105,12 @@ class LxmfAdapter : public IMeshAdapter using PropagationPeerState = runtime::PropagationPeerState; using PendingPropagationUpload = runtime::PendingPropagationUpload; using PropagationSyncStage = runtime::PropagationSyncStage; + using PendingNomadPageRequest = runtime::PendingNomadPageRequest; + + static bool resolveLocalDestinationForAnnounce( + void* context, + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + LocalDestinationKind* out_kind); static constexpr uint32_t kAnnounceIntervalMs = 120000; static constexpr uint32_t kInitialAnnounceDelayMs = 1500; @@ -140,27 +155,6 @@ class LxmfAdapter : public IMeshAdapter uint8_t packet_hash[reticulum::kFullHashSize] = {}; }; - struct PendingNomadPageRequest - { - uint8_t destination_hash[reticulum::kTruncatedHashSize] = {}; - uint8_t request_id[reticulum::kTruncatedHashSize] = {}; - char path[kNomadPagePathMaxLen] = {}; - uint32_t created_ms = 0; - uint32_t last_attempt_ms = 0; - uint32_t last_path_request_ms = 0; - bool path_requested = false; - bool link_started = false; - bool request_sent = false; - }; - - struct PendingPingRequest - { - uint8_t destination_hash[reticulum::kTruncatedHashSize] = {}; - uint32_t created_ms = 0; - uint32_t last_path_request_ms = 0; - uint32_t last_send_attempt_ms = 0; - }; - struct OutboundLxmfDispatch { bool ok = false; @@ -178,7 +172,6 @@ class LxmfAdapter : public IMeshAdapter uint8_t announce_tx_signed_scratch_[reticulum::kReticulumMtu] = {}; uint8_t announce_tx_payload_scratch_[reticulum::kReticulumMtu] = {}; uint8_t announce_tx_packet_scratch_[reticulum::kReticulumMtu] = {}; - uint8_t announce_rx_signed_scratch_[reticulum::kReticulumMtu] = {}; sys::RingBuffer deferred_discovery_queue_; DeferredDiscoveryPacket deferred_discovery_scratch_{}; LxmfIdentity identity_; @@ -186,12 +179,15 @@ class LxmfAdapter : public IMeshAdapter static constexpr std::size_t kIncomingQueueDepth = 12; ::chat::infra::IncomingTextQueue text_receive_queue_; ::chat::infra::IncomingDataQueue data_receive_queue_; - std::vector peers_; - runtime::TransportRuntime transport_; - runtime::LinkRuntime links_; - runtime::PropagationRuntime propagation_; - runtime::PropagationStampRuntime propagation_stamp_; - PeerInfo propagation_peer_scratch_{}; + runtime::DestinationRegistry destination_registry_; + runtime::PathManager path_manager_; + runtime::LinkManager link_manager_; + runtime::AnnounceIngestor announce_ingestor_; + runtime::ReticulumPacketRouter packet_router_; + runtime::PingService ping_service_; + runtime::NetworkPageClient network_page_client_; + runtime::PropagationClient propagation_client_; + runtime::LxstTelephonyClient lxst_telephony_client_; std::string user_long_name_; std::string user_short_name_; uint32_t last_announce_ms_ = 0; @@ -214,8 +210,6 @@ class LxmfAdapter : public IMeshAdapter std::array pending_peer_projection_nodes_{}; std::size_t pending_peer_projection_count_ = 0; std::array peer_directory_load_entries_{}; - std::vector pending_ping_requests_; - std::vector pending_nomad_page_requests_; uint8_t nomad_page_request_payload_scratch_[reticulum::kReticulumMtu] = {}; uint8_t nomad_page_wire_payload_scratch_[reticulum::kReticulumMtu] = {}; uint8_t nomad_page_packet_scratch_[reticulum::kReticulumMtu] = {}; @@ -223,7 +217,6 @@ class LxmfAdapter : public IMeshAdapter uint8_t link_request_packet_scratch_[reticulum::kReticulumMtu] = {}; uint8_t link_request_routed_scratch_[reticulum::kReticulumMtu] = {}; std::size_t link_request_packet_len_ = 0; - uint8_t call_wire_scratch_[reticulum::kReticulumMtu] = {}; uint32_t last_peer_projection_ms_ = 0; uint32_t next_app_packet_id_ = 1; bool announce_pending_ = true; @@ -321,8 +314,8 @@ class LxmfAdapter : public IMeshAdapter const PropagationPeerState& node); bool sendPropagationSyncRequest(LinkSession& session, PropagationSyncStage next_stage, - const std::vector>* wants, - const std::vector>* haves, + const runtime::PropagationIdList* wants, + const runtime::PropagationIdList* haves, bool include_transfer_limit); void processPropagationSyncResponse(LinkSession& session); bool respondToSidebandTelemetryRequest( @@ -440,7 +433,7 @@ class LxmfAdapter : public IMeshAdapter void pumpPendingPingRequests(); void pumpNomadPageRequests(); void completeNomadPageRequest(PendingNomadPageRequest& request, - const std::vector& packed_response); + const runtime::ResourcePayloadBuffer& packed_response); PendingNomadPageRequest* findPendingNomadPageRequestById( const uint8_t destination_hash[reticulum::kTruncatedHashSize], const uint8_t* request_id, diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_announce_ingestor.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_announce_ingestor.h new file mode 100644 index 00000000..4908158f --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_announce_ingestor.h @@ -0,0 +1,87 @@ +/** + * @file lxmf_announce_ingestor.h + * @brief Verified announce ingestion owner for embedded LXMF. + */ + +#pragma once + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_destination_registry.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_identity.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_path_manager.h" +#include "platform/esp/arduino_common/chat/infra/reticulum/reticulum_interfaces.h" + +namespace chat::lxmf::runtime +{ + +using LocalDestinationResolver = + bool (*)(void* context, + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + LocalDestinationKind* out_kind); + +struct AnnounceIngestOptions +{ + uint32_t now_ms = 0; + uint32_t now_s = 0; + uint32_t path_ttl_ms = 0; + uint32_t directory_address_refresh_interval_s = 0; + std::size_t max_paths = 0; + uint8_t max_transport_hops = 0; + reticulum::interfaces::InterfaceId ingress_interface_id = + reticulum::interfaces::kInvalidInterfaceId; + reticulum::interfaces::InterfaceKind ingress_interface = + reticulum::interfaces::InterfaceKind::LoRa; + void* local_destination_context = nullptr; + LocalDestinationResolver resolve_local_destination = nullptr; +}; + +struct AnnounceIngestResult +{ + enum class Status + { + Rejected, + Ignored, + Accepted, + }; + + Status status = Status::Rejected; + const char* reason = "invalid"; + reticulum::ParsedAnnounce announce{}; + uint8_t identity_hash[reticulum::kTruncatedHashSize] = {}; + uint8_t expected_destination_hash[reticulum::kTruncatedHashSize] = {}; + bool local_destination = false; + LocalDestinationKind local_kind = LocalDestinationKind::Delivery; + PathAnnounceDecision path_decision = PathAnnounceDecision::RejectReplay; + PathEntry* path = nullptr; + bool delivery_announce = false; + bool propagation_announce = false; + bool call_audio_announce = false; + bool lxst_telephony_announce = false; + bool nomad_node_announce = false; + bool contact_announce = false; + bool packet_has_ratchet = false; + char display_name[32] = {}; + PeerInfo* learned_peer = nullptr; + bool identity_changed = false; + bool ratchet_changed = false; + bool display_changed = false; + bool address_refresh_due = false; + bool should_store_address = false; +}; + +class AnnounceIngestor +{ + public: + bool ingest(const uint8_t* raw_packet, + std::size_t raw_len, + const reticulum::ParsedPacket& packet, + const LxmfIdentity& local_identity, + DestinationRegistry& destination_registry, + PathManager& path_manager, + const AnnounceIngestOptions& options, + AnnounceIngestResult* out_result); + + private: + uint8_t signed_scratch_[reticulum::kReticulumMtu] = {}; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_destination_registry.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_destination_registry.h new file mode 100644 index 00000000..363a784c --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_destination_registry.h @@ -0,0 +1,62 @@ +/** + * @file lxmf_destination_registry.h + * @brief Destination and identity registry owner for the embedded LXMF runtime. + */ + +#pragma once + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_runtime_state.h" + +#include +#include + +namespace chat::lxmf::runtime +{ + +class DestinationRegistry +{ + public: + DestinationRegistry() = default; + DestinationRegistry(const DestinationRegistry&) = delete; + DestinationRegistry& operator=(const DestinationRegistry&) = delete; + DestinationRegistry(DestinationRegistry&&) = delete; + DestinationRegistry& operator=(DestinationRegistry&&) = delete; + + std::size_t size() const; + void clear(); + + PeerInfo* findByNodeId(NodeId node_id); + const PeerInfo* findByNodeId(NodeId node_id) const; + PeerInfo* findByDestinationHash( + const uint8_t hash[reticulum::kTruncatedHashSize]); + const PeerInfo* findByDestinationHash( + const uint8_t hash[reticulum::kTruncatedHashSize]) const; + const PeerInfo* findByIdentityHash( + const uint8_t hash[reticulum::kTruncatedHashSize]) const; + + PeerInfo& upsertDestination( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]); + + template + void forEach(Fn&& fn) + { + for (auto& peer : peers_) + { + fn(peer); + } + } + + template + void forEach(Fn&& fn) const + { + for (const auto& peer : peers_) + { + fn(peer); + } + } + + private: + std::vector peers_; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_manager.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_manager.h new file mode 100644 index 00000000..aa4adc5f --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_manager.h @@ -0,0 +1,207 @@ +/** + * @file lxmf_link_manager.h + * @brief Link session owner for the embedded LXMF runtime. + */ + +#pragma once + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_runtime.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h" + +namespace chat::lxmf::runtime +{ + +class LinkManager +{ + public: + LinkManager() = default; + LinkManager(const LinkManager&) = delete; + LinkManager& operator=(const LinkManager&) = delete; + LinkManager(LinkManager&&) = delete; + LinkManager& operator=(LinkManager&&) = delete; + + std::size_t size() const; + void clear(); + + LinkSession* findSession( + const uint8_t link_id[reticulum::kTruncatedHashSize]); + LinkSession* findOpenSessionByDestination( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + LocalDestinationKind kind); + LinkSession* findActiveSessionByDestination( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + LocalDestinationKind kind); + + LinkSession* appendSession(std::size_t max_link_sessions); + LinkSession* appendSessionPreserving( + std::size_t max_link_sessions, + const uint8_t preserve_link_id[reticulum::kTruncatedHashSize]); + void discardLastSession(); + + bool closeSession(LinkSession& session, + LinkCloseReason reason, + uint32_t now_ms); + void cullSessionTables(LinkSession& session, + uint32_t now_ms, + const LinkRuntimeLimits& limits); + void cullResources(LinkSession& session, + uint32_t now_ms, + const ResourceRuntimeLimits& limits); + LinkRuntimeMaintenance advanceSessionLifecycle( + LinkSession& session, + uint32_t now_ms, + const LinkRuntimeLimits& limits); + void markSessionStale(LinkSession& session); + void removeExpiredSessions(uint32_t now_ms, + const LinkRuntimeLimits& limits); + LinkResourceTransfer* findIncomingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]); + const LinkResourceTransfer* findIncomingResource( + const LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) const; + LinkResourceTransfer* findOutgoingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]); + const LinkResourceTransfer* findOutgoingResource( + const LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) const; + bool eraseIncomingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]); + bool eraseOutgoingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]); + LinkResourceTransfer* startIncomingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize], + const uint8_t random_hash[kResourceMapHashLen], + const uint8_t original_hash[reticulum::kFullHashSize], + const uint8_t* request_id, + std::size_t request_id_len, + const uint8_t* hashmap, + std::size_t hashmap_len, + uint32_t data_size, + uint32_t transfer_size, + uint32_t part_count, + uint32_t segment_index, + uint32_t total_segments, + uint8_t flags, + bool encrypted, + bool compressed, + bool has_metadata, + bool split, + uint32_t now_ms, + uint32_t window_size); + bool initialiseOutgoingResource(LinkResourceTransfer& resource, + const uint8_t* request_id, + std::size_t request_id_len, + uint32_t data_size, + uint32_t transfer_size, + uint32_t part_count, + uint8_t flags, + uint32_t now_ms, + uint32_t window_size); + LinkResourceTransfer* appendOutgoingResource(LinkSession& session, + LinkResourceTransfer&& resource); + bool discardLastOutgoingResource(LinkSession& session); + ResourceWindowRequest buildNextResourceWindowRequest( + const LinkResourceTransfer& resource) const; + void noteResourceWindowRequested(LinkResourceTransfer& resource, + bool waiting_for_hashmap, + uint32_t now_ms); + bool applyIncomingResourceHashmapUpdate(LinkResourceTransfer& resource, + uint32_t segment, + const uint8_t* hashmap, + std::size_t hashmap_len, + std::size_t segment_capacity, + uint32_t now_ms); + bool recordIncomingResourcePart(LinkResourceTransfer& resource, + const uint8_t* payload, + std::size_t payload_len, + const uint8_t full_hash[reticulum::kFullHashSize], + uint32_t now_ms, + std::size_t* out_matched_index, + bool* out_complete); + void markResourceComplete(LinkResourceTransfer& resource, uint32_t now_ms); + ResourceAssemblyResult appendResourceAssemblySegment( + LinkSession& session, + LinkResourceTransfer& resource, + ResourcePayloadBuffer& payload_data, + uint32_t now_ms); + bool markOutgoingResourceProofReceived( + LinkResourceTransfer& resource, + const uint8_t expected_proof[reticulum::kFullHashSize], + uint32_t now_ms); + uint32_t takeResourceMessageId(LinkResourceTransfer& resource); + void touchResource(LinkResourceTransfer& resource, uint32_t now_ms); + + template + void takeTrackedOutgoingResourceMessageIds(LinkSession& session, Fn&& fn) + { + for (auto& resource : session.outgoing_resources) + { + if (resource.message_id != 0) + { + fn(resource.message_id); + resource.message_id = 0; + } + } + } + + template + void takeExpiredOutgoingResourceMessageIds(LinkSession& session, + uint32_t now_ms, + uint32_t ttl_ms, + Fn&& fn) + { + for (auto& resource : session.outgoing_resources) + { + if (resource.message_id != 0 && + (resource.last_activity_ms == 0 || + now_ms - resource.last_activity_ms > ttl_ms)) + { + fn(resource.message_id); + resource.message_id = 0; + } + } + } + + template + void forEachIncomingResource(LinkSession& session, Fn&& fn) + { + for (auto& resource : session.incoming_resources) + { + if (!fn(resource)) + { + break; + } + } + } + + template + void forEachSession(Fn&& fn) + { + for (auto& session : links_.sessions) + { + fn(session); + } + } + + template + void forEachSession(Fn&& fn) const + { + for (const auto& session : links_.sessions) + { + fn(session); + } + } + + private: + bool ensureCapacity(std::size_t max_link_sessions, + const uint8_t preserve_link_id[reticulum::kTruncatedHashSize]); + + LinkRuntime links_; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_lxst_telephony_client.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_lxst_telephony_client.h new file mode 100644 index 00000000..55151e76 --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_lxst_telephony_client.h @@ -0,0 +1,32 @@ +/** + * @file lxmf_lxst_telephony_client.h + * @brief Sideband/LXST telephony runtime owner. + */ + +#pragma once + +#include "chat/infra/lxmf/lxmf_wire.h" + +#include + +namespace chat::lxmf::runtime +{ + +class LxstTelephonyClient +{ + public: + LxstTelephonyClient() = default; + LxstTelephonyClient(const LxstTelephonyClient&) = delete; + LxstTelephonyClient& operator=(const LxstTelephonyClient&) = delete; + LxstTelephonyClient(LxstTelephonyClient&&) = delete; + LxstTelephonyClient& operator=(LxstTelephonyClient&&) = delete; + + uint8_t* scratch(); + const uint8_t* scratch() const; + std::size_t scratchCapacity() const; + + private: + uint8_t scratch_[reticulum::kReticulumMtu] = {}; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_memory.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_memory.h index 8807947c..14b55eac 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_memory.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_memory.h @@ -5,21 +5,33 @@ #pragma once +#include "chat/infra/lxmf/lxmf_wire.h" + +#include #include #include +#include #include #include +#include #include #if defined(ESP_PLATFORM) #include -#else -#include #endif namespace chat::lxmf::runtime { +[[noreturn]] inline void allocation_failed() +{ +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) + throw std::bad_alloc(); +#else + std::abort(); +#endif +} + template class PsramAllocator { @@ -37,7 +49,7 @@ class PsramAllocator { if (count > std::numeric_limits::max() / sizeof(T)) { - throw std::bad_alloc(); + allocation_failed(); } const std::size_t bytes = count * sizeof(T); @@ -53,7 +65,7 @@ class PsramAllocator #endif if (!ptr) { - throw std::bad_alloc(); + allocation_failed(); } return static_cast(ptr); } @@ -90,7 +102,57 @@ bool operator!=(const PsramAllocator&, const PsramAllocator&) noexcept return false; } -using ResourcePayloadBuffer = std::vector>; -using ResourcePayloadList = std::vector; +using RuntimeByteBuffer = std::vector>; +using RuntimeByteBufferList = + std::vector>; +using RuntimeByteSpanList = + std::vector<::chat::lxmf::ByteSpan, PsramAllocator<::chat::lxmf::ByteSpan>>; +using RuntimeMapHash = std::array; +using RuntimeMapHashList = + std::vector>; +using ResourcePayloadBuffer = RuntimeByteBuffer; +using ResourcePayloadList = RuntimeByteBufferList; +using ResourceMetadataBuffer = RuntimeByteBuffer; +using ResourceBitmapBuffer = RuntimeByteBuffer; +using ResourceMapHashList = RuntimeMapHashList; +using PropagationIdList = RuntimeByteBufferList; +using PropagationMessageList = RuntimeByteBufferList; + +inline bool appendRuntimeByteBufferCallback(const uint8_t* data, + std::size_t len, + void* context) +{ + auto* items = static_cast(context); + if (!items || (!data && len != 0U)) + { + return false; + } + + RuntimeByteBuffer item; + if (len != 0U) + { + item.assign(data, data + len); + } + items->push_back(std::move(item)); + return true; +} + +inline RuntimeByteSpanList makeRuntimeByteSpans( + const RuntimeByteBufferList& items) +{ + RuntimeByteSpanList spans; + spans.reserve(items.size()); + for (const auto& item : items) + { + spans.push_back(::chat::lxmf::ByteSpan{item.data(), item.size()}); + } + return spans; +} + +inline ::chat::lxmf::ByteSpanList viewRuntimeByteSpans( + const RuntimeByteSpanList& spans) +{ + return ::chat::lxmf::ByteSpanList{spans.data(), spans.size()}; +} } // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_network_page_client.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_network_page_client.h new file mode 100644 index 00000000..b1da3cf7 --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_network_page_client.h @@ -0,0 +1,90 @@ +/** + * @file lxmf_network_page_client.h + * @brief Pending Nomad/Network page request owner. + */ + +#pragma once + +#include "chat/infra/lxmf/lxmf_wire.h" + +#include +#include +#include + +namespace chat::lxmf::runtime +{ + +struct PendingNomadPageRequest +{ + uint8_t destination_hash[reticulum::kTruncatedHashSize] = {}; + uint8_t request_id[reticulum::kTruncatedHashSize] = {}; + char path[64] = {}; + uint32_t created_ms = 0; + uint32_t last_attempt_ms = 0; + uint32_t last_path_request_ms = 0; + bool path_requested = false; + bool link_started = false; + bool request_sent = false; +}; + +enum class NetworkPageQueueResult +{ + Queued, + Duplicate, + Full, + Invalid, +}; + +class NetworkPageClient +{ + public: + NetworkPageClient() = default; + NetworkPageClient(const NetworkPageClient&) = delete; + NetworkPageClient& operator=(const NetworkPageClient&) = delete; + NetworkPageClient(NetworkPageClient&&) = delete; + NetworkPageClient& operator=(NetworkPageClient&&) = delete; + + std::size_t size() const; + bool empty() const; + void clear(); + + NetworkPageQueueResult queue( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const char* path, + uint32_t now_ms, + std::size_t max_pending, + std::size_t max_path_len, + PendingNomadPageRequest** out_request); + + PendingNomadPageRequest* at(std::size_t index); + const PendingNomadPageRequest* at(std::size_t index) const; + void eraseAt(std::size_t index); + + PendingNomadPageRequest* findByRequestId( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const uint8_t* request_id, + std::size_t request_id_len); + + template + void forEach(Fn&& fn) + { + for (auto& request : pending_) + { + fn(request); + } + } + + template + void forEach(Fn&& fn) const + { + for (const auto& request : pending_) + { + fn(request); + } + } + + private: + std::vector pending_; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_packet_router.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_packet_router.h new file mode 100644 index 00000000..2efc4b12 --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_packet_router.h @@ -0,0 +1,28 @@ +/** + * @file lxmf_packet_router.h + * @brief Single routing decision point for Reticulum packets entering LXMF. + */ + +#pragma once + +#include "chat/infra/lxmf/lxmf_wire.h" + +namespace chat::lxmf::runtime +{ + +enum class PacketRoute +{ + Announce, + Proof, + LinkRequest, + Data, + LinkOrTransport, +}; + +class ReticulumPacketRouter +{ + public: + PacketRoute route(const reticulum::ParsedPacket& packet) const; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_path_manager.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_path_manager.h new file mode 100644 index 00000000..6ea64939 --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_path_manager.h @@ -0,0 +1,130 @@ +/** + * @file lxmf_path_manager.h + * @brief Path, packet-filter, proof-route, receipt, and link-relay owner. + */ + +#pragma once + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_transport_runtime.h" + +namespace chat::lxmf::runtime +{ + +class PathManager +{ + public: + PathManager() = default; + PathManager(const PathManager&) = delete; + PathManager& operator=(const PathManager&) = delete; + PathManager(PathManager&&) = delete; + PathManager& operator=(PathManager&&) = delete; + + bool isDuplicatePacket( + const uint8_t packet_hash[reticulum::kFullHashSize]) const; + void rememberPacket( + const uint8_t packet_hash[reticulum::kFullHashSize], + uint32_t now_ms, + std::size_t max_packet_filter); + void forgetPacket( + const uint8_t packet_hash[reticulum::kFullHashSize]); + + void rememberReversePath( + const uint8_t proof_hash[reticulum::kTruncatedHashSize], + uint8_t interface_id, + uint8_t expected_hops, + uint32_t now_ms, + std::size_t max_reverse_entries); + ReverseEntry* findReversePath( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]); + + PendingPathRequest* findPendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]); + const PendingPathRequest* findPendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) const; + void notePendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t now_ms, + std::size_t max_pending_path_requests); + void resolvePendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]); + + void notePendingPingReceipt( + const uint8_t packet_hash[reticulum::kFullHashSize], + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const uint8_t peer_sig_pub[LxmfIdentity::kSigPubKeySize], + uint32_t now_ms, + std::size_t max_pending_ping_receipts); + PendingPingReceipt* findPendingPingReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]); + void removePendingPingReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]); + + void notePendingDeliveryReceipt( + const uint8_t packet_hash[reticulum::kFullHashSize], + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const uint8_t peer_sig_pub[LxmfIdentity::kSigPubKeySize], + MessageId message_id, + uint32_t now_ms, + std::size_t max_pending_delivery_receipts); + PendingDeliveryReceipt* findPendingDeliveryReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]); + void removePendingDeliveryReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]); + + PathEntry& upsertPath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + std::size_t max_paths); + const PathEntry* findPath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t now_ms, + uint32_t path_ttl_ms) const; + const PathEntry* findAnyPath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) const; + void expirePath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]); + void clearPaths(); + + LinkRelayEntry& upsertLinkRelay( + const uint8_t link_id[reticulum::kTruncatedHashSize], + std::size_t max_link_relays); + LinkRelayEntry* findLinkRelay( + const uint8_t link_id[reticulum::kTruncatedHashSize]); + void removeLinkRelay( + const uint8_t link_id[reticulum::kTruncatedHashSize]); + void clearReversePathAndRelays(); + + void cull(uint32_t now_ms, const TransportRuntimeLimits& limits); + void clear(); + + template + void forEachPath(Fn&& fn) const + { + for (const auto& path : transport_.paths) + { + fn(path); + } + } + + template + void forEachPendingPingReceipt(Fn&& fn) const + { + for (const auto& receipt : transport_.pending_ping_receipts) + { + fn(receipt); + } + } + + template + void forEachPendingDeliveryReceipt(Fn&& fn) const + { + for (const auto& receipt : transport_.pending_delivery_receipts) + { + fn(receipt); + } + } + + private: + TransportRuntime transport_; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_ping_service.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_ping_service.h new file mode 100644 index 00000000..8ec1a1bd --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_ping_service.h @@ -0,0 +1,113 @@ +/** + * @file lxmf_ping_service.h + * @brief Pending Reticulum ping request owner. + */ + +#pragma once + +#include "chat/infra/lxmf/lxmf_wire.h" + +#include +#include +#include + +namespace chat::lxmf::runtime +{ + +struct PendingPingRequest +{ + uint8_t destination_hash[reticulum::kTruncatedHashSize] = {}; + uint32_t created_ms = 0; + uint32_t last_path_request_ms = 0; + uint32_t last_send_attempt_ms = 0; +}; + +enum class PendingPingQueueResult +{ + Queued, + Duplicate, + Full, + Invalid, +}; + +class PingService +{ + public: + PingService() = default; + PingService(const PingService&) = delete; + PingService& operator=(const PingService&) = delete; + PingService(PingService&&) = delete; + PingService& operator=(PingService&&) = delete; + + std::size_t size() const; + void clear(); + + PendingPingQueueResult queue( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t now_ms, + std::size_t max_pending); + + template + void pump(uint32_t now_ms, + bool paused, + uint32_t ttl_ms, + uint32_t send_retry_ms, + uint32_t path_retry_ms, + PeerReadyFn&& peer_ready, + DispatchFn&& dispatch, + PathRetryFn&& path_retry, + TimeoutFn&& timeout) + { + for (std::size_t index = 0; index < pending_.size();) + { + PendingPingRequest& request = pending_[index]; + const uint32_t elapsed_ms = now_ms - request.created_ms; + + if (request.created_ms == 0 || elapsed_ms > ttl_ms) + { + timeout(request, elapsed_ms); + pending_.erase(pending_.begin() + static_cast(index)); + continue; + } + + if (paused) + { + ++index; + continue; + } + + const bool ready = peer_ready(request.destination_hash); + if (ready && + (request.last_send_attempt_ms == 0 || + (now_ms - request.last_send_attempt_ms) >= send_retry_ms)) + { + request.last_send_attempt_ms = now_ms; + if (dispatch(request.destination_hash, + request.created_ms, + elapsed_ms)) + { + pending_.erase(pending_.begin() + + static_cast(index)); + continue; + } + } + else if (!ready && + (request.last_path_request_ms == 0 || + (now_ms - request.last_path_request_ms) >= path_retry_ms)) + { + request.last_path_request_ms = now_ms; + path_retry(request.destination_hash, elapsed_ms); + } + + ++index; + } + } + + private: + std::vector pending_; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_client.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_client.h new file mode 100644 index 00000000..058b5864 --- /dev/null +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_client.h @@ -0,0 +1,139 @@ +/** + * @file lxmf_propagation_client.h + * @brief Propagation runtime state owner. + */ + +#pragma once + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_runtime.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_service_runtime.h" +#if !defined(TRAIL_MATE_RETICULUM_PARSE_ONLY) +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_stamp_runtime.h" +#endif + +#include +#include +#include + +namespace chat::lxmf::runtime +{ + +struct PropagationActivePeerSelection +{ + const PropagationPeerState* peer = nullptr; + bool changed = false; +}; + +class PropagationClient +{ + public: + PropagationClient() = default; + PropagationClient(const PropagationClient&) = delete; + PropagationClient& operator=(const PropagationClient&) = delete; + PropagationClient(PropagationClient&&) = delete; + PropagationClient& operator=(PropagationClient&&) = delete; + + PropagationRuntime& state(); + const PropagationRuntime& state() const; + + PropagationActivePeerSelection selectActivePeer( + bool automatic, + const uint8_t configured_hash[reticulum::kTruncatedHashSize], + uint32_t now_s, + uint32_t peer_ttl_s, + bool sync_on_start); + void clearActivePeer(); + + bool canQueueUpload(std::size_t max_pending) const; + PendingPropagationUpload* queueUpload(PendingPropagationUpload upload, + std::size_t max_pending); + bool hasPendingUploads() const; + PendingPropagationUpload* firstPendingUpload(); + const PendingPropagationUpload* firstPendingUpload() const; + bool removeFirstPendingUpload(); + void markExpiredUploads(uint32_t now_ms, uint32_t ttl_ms); + std::vector takeFailedUploads(); + std::vector takeAllPendingUploads(); + void resetStampingUploads(); + + void resetForDisabled(); + void resetForNetworkConfig(bool sync_on_start); + bool syncDue(uint32_t now_s, uint32_t sync_interval_s) const; + PropagationSyncStage syncStage() const; + const PropagationIdList& syncWants() const; + const PropagationIdList& syncHaves() const; + bool syncHavesEmpty() const; + std::size_t pendingDeliveryCount() const; + bool startSyncIfDue(uint32_t now_s, + uint32_t now_ms, + uint32_t sync_interval_s); + void markSyncRequestSent( + const uint8_t request_id[reticulum::kTruncatedHashSize], + PropagationSyncStage next_stage); + bool syncRequestMatches(const LinkPendingRequest& request) const; + void markSyncFailed(); + void noteListingResult(const PropagationIdList& remote_ids, + std::size_t max_messages); + bool registerDeliveryCommit( + const uint8_t transient_id[reticulum::kFullHashSize], + const uint8_t message_hash[reticulum::kFullHashSize], + std::size_t max_pending); + void rememberDeliveredTransient( + const uint8_t transient_id[reticulum::kFullHashSize], + uint32_t now_s, + std::size_t max_transients); + void noteDownloadResult(bool registration_failed, uint32_t now_ms); + bool pollPersistence(uint32_t now_ms, uint32_t ttl_ms); + bool noteDeliveryCommit(const uint8_t message_hash[reticulum::kFullHashSize], + bool accepted, + uint32_t now_s, + std::size_t max_transients); + void markAcknowledged(); + std::size_t syncHaveCount() const; + void finishSyncComplete(uint32_t now_s); + void finishSyncFailed(uint32_t now_s); + void cull(uint32_t now_s, const PropagationRuntimeLimits& limits); + const PropagationPeerState* notePeerAnnounce( + const uint8_t propagation_hash[reticulum::kTruncatedHashSize], + const uint8_t delivery_hash[reticulum::kTruncatedHashSize], + const uint8_t identity_hash[reticulum::kTruncatedHashSize], + uint8_t hops, + const DecodedPropagationAnnounce& announce_data, + const uint8_t* public_key, + uint32_t now_s, + std::size_t max_peers); + bool planBatchAcceptance( + const uint8_t* plaintext, + std::size_t plaintext_len, + const PropagationBatchContext& context, + const PropagationBatchLimits& limits, + PropagationBatchAcceptance* out_acceptance); + void noteLocalDeliveryResult( + const uint8_t transient_id[reticulum::kFullHashSize], + bool delivered, + uint32_t now_s, + std::size_t max_transients); + void noteBatchHandled(const PropagationBatchAcceptance& acceptance); + bool planServiceResponse(const DecodedLinkRequest& request, + const PropagationServicePeerContext& peer_context, + uint32_t now_s, + const PropagationServiceLimits& limits, + PropagationServiceResponse* out_response); + +#if !defined(TRAIL_MATE_RETICULUM_PARSE_ONLY) + PropagationStampRuntime& stamp(); + const PropagationStampRuntime& stamp() const; +#endif + + PeerInfo& peerScratch(); + const PeerInfo& peerScratch() const; + + private: + PropagationRuntime state_; +#if !defined(TRAIL_MATE_RETICULUM_PARSE_ONLY) + PropagationStampRuntime stamp_; +#endif + PeerInfo peer_scratch_{}; +}; + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_runtime.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_runtime.h index 57a7a5cf..8b3c837e 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_runtime.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_runtime.h @@ -26,7 +26,7 @@ struct PropagationRuntimeLimits struct PropagationMessageSelection { - std::vector messages; + RuntimeByteSpanList messages; uint32_t served_count = 0; }; @@ -101,15 +101,15 @@ std::size_t removePropagationEntriesForDestination( const uint8_t transient_id[reticulum::kFullHashSize], const uint8_t destination_hash[reticulum::kTruncatedHashSize]); -std::vector> collectMissingPropagationTransientIds( +PropagationIdList collectMissingPropagationTransientIds( const PropagationRuntime& propagation, - const std::vector>& transient_ids); -std::vector> collectPropagationEntryIdsForDestination( + const PropagationIdList& transient_ids); +PropagationIdList collectPropagationEntryIdsForDestination( const PropagationRuntime& propagation, const uint8_t destination_hash[reticulum::kTruncatedHashSize]); PropagationMessageSelection collectPropagationMessagesForWants( PropagationRuntime& propagation, - const std::vector>& transient_ids, + const PropagationIdList& transient_ids, const uint8_t destination_hash[reticulum::kTruncatedHashSize], std::size_t transfer_limit_bytes, std::size_t base_response_size, diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h index 755e0c95..aa7a447d 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h @@ -58,8 +58,10 @@ bool initialiseIncomingResourceTransfer( const uint8_t resource_hash[reticulum::kFullHashSize], const uint8_t random_hash[kResourceMapHashLen], const uint8_t original_hash[reticulum::kFullHashSize], - std::vector&& request_id, - std::vector&& hashmap, + const uint8_t* request_id, + std::size_t request_id_len, + const uint8_t* hashmap, + std::size_t hashmap_len, uint32_t data_size, uint32_t transfer_size, uint32_t part_count, @@ -92,7 +94,8 @@ void noteResourceWindowRequest(LinkResourceTransfer& resource, bool applyResourceHashmapUpdate(LinkResourceTransfer& resource, uint32_t segment, - const std::vector& hashmap, + const uint8_t* hashmap, + std::size_t hashmap_len, std::size_t segment_capacity, uint32_t now_ms); diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_runtime_state.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_runtime_state.h index cb460f21..e7e2c5ff 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_runtime_state.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_runtime_state.h @@ -134,17 +134,17 @@ enum class LinkCloseReason : uint8_t struct LinkPendingRequest { - std::vector request_id; + ResourceMetadataBuffer request_id; uint32_t created_ms = 0; bool awaiting_resource = false; bool response_ready = false; - std::vector response; + ResourcePayloadBuffer response; }; struct DeferredLinkPayload { ResourcePayloadBuffer payload; - std::vector request_id; + ResourceMetadataBuffer request_id; uint32_t message_id = 0; uint8_t resource_flags = 0; }; @@ -162,12 +162,12 @@ struct LinkResourceTransfer uint8_t random_hash[4] = {}; uint8_t original_hash[reticulum::kFullHashSize] = {}; uint8_t expected_proof[reticulum::kFullHashSize] = {}; - std::vector request_id; - std::vector hashmap; - std::vector> map_hashes; - std::vector map_hash_known; + ResourceMetadataBuffer request_id; + ResourceMetadataBuffer hashmap; + ResourceMapHashList map_hashes; + ResourceBitmapBuffer map_hash_known; ResourcePayloadList parts; - std::vector received_bitmap; + ResourceBitmapBuffer received_bitmap; uint32_t data_size = 0; uint32_t transfer_size = 0; uint32_t part_count = 0; @@ -194,7 +194,7 @@ struct LinkResourceTransfer struct LinkResourceAssembly { uint8_t original_hash[reticulum::kFullHashSize] = {}; - std::vector request_id; + ResourceMetadataBuffer request_id; ResourcePayloadBuffer payload; uint32_t next_segment_index = 1; uint32_t total_segments = 1; @@ -360,8 +360,8 @@ struct PropagationRuntime std::vector peers; std::vector pending_uploads; std::vector pending_deliveries; - std::vector> sync_wants; - std::vector> sync_haves; + PropagationIdList sync_wants; + PropagationIdList sync_haves; uint8_t active_node_hash[reticulum::kTruncatedHashSize] = {}; uint8_t sync_request_id[reticulum::kTruncatedHashSize] = {}; uint32_t last_sync_s = 0; diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp index 408a8a18..4a773902 100644 --- a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp @@ -39,6 +39,11 @@ #include #include #include +#include + +#ifndef TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT +#define TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT 0 +#endif namespace chat::lxmf { @@ -118,24 +123,6 @@ constexpr const char* kAnonymousPeerDisplayName = "Anonymous Peer"; constexpr const char* kAnonymousNodeDisplayName = "Anonymous Node"; constexpr uint8_t kPropagationMetaName = 0x01; -const char* pathAnnounceDecisionLabel(runtime::PathAnnounceDecision decision) -{ - switch (decision) - { - case runtime::PathAnnounceDecision::AcceptNew: - return "new"; - case runtime::PathAnnounceDecision::AcceptNewer: - return "newer"; - case runtime::PathAnnounceDecision::AcceptExpired: - return "expired"; - case runtime::PathAnnounceDecision::RejectReplay: - return "replay"; - case runtime::PathAnnounceDecision::RejectStale: - return "stale"; - } - return "unknown"; -} - void formatHashPrefix(const uint8_t* hash, char* out, size_t out_len) { if (!out || out_len == 0) @@ -181,10 +168,11 @@ void formatHashHex(const uint8_t* hash, size_t hash_len, char* out, size_t out_l } } -bool decodeMsgpackByteString(const std::vector& packed, - std::vector* out) +bool decodeMsgpackByteString(const uint8_t* packed, + size_t packed_len, + runtime::ResourcePayloadBuffer* out) { - if (packed.empty() || !out) + if (!packed || packed_len == 0 || !out) { return false; } @@ -198,7 +186,7 @@ bool decodeMsgpackByteString(const std::vector& packed, } else if (marker == 0xC4 || marker == 0xD9) { - if (packed.size() < 2) + if (packed_len < 2) { return false; } @@ -207,7 +195,7 @@ bool decodeMsgpackByteString(const std::vector& packed, } else if (marker == 0xC5 || marker == 0xDA) { - if (packed.size() < 3) + if (packed_len < 3) { return false; } @@ -217,7 +205,7 @@ bool decodeMsgpackByteString(const std::vector& packed, } else if (marker == 0xC6 || marker == 0xDB) { - if (packed.size() < 5) + if (packed_len < 5) { return false; } @@ -232,11 +220,11 @@ bool decodeMsgpackByteString(const std::vector& packed, return false; } - if (offset > packed.size() || len > (packed.size() - offset)) + if (offset > packed_len || len > (packed_len - offset)) { return false; } - out->assign(packed.data() + offset, packed.data() + offset + len); + out->assign(packed + offset, packed + offset + len); return true; } @@ -555,11 +543,15 @@ void callDestinationHashForIdentity( ReticulumCallWireProfile profile, uint8_t out_hash[reticulum::kTruncatedHashSize]) { +#if TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT if (profile == ReticulumCallWireProfile::MeshChatCallAudio) { destinationHashForServiceAspect(identity_hash, "call", "audio", out_hash); } else +#else + (void)profile; +#endif { destinationHashForServiceAspect(identity_hash, "lxst", "telephony", out_hash); } @@ -702,7 +694,7 @@ bool unpackRnsLxmfEnvelope(const uint8_t expected_destination_hash[reticulum::kT return false; } - std::vector full_payload(reticulum::kTruncatedHashSize + payload_len); + runtime::RuntimeByteBuffer full_payload(reticulum::kTruncatedHashSize + payload_len); memcpy(full_payload.data(), expected_destination_hash, reticulum::kTruncatedHashSize); memcpy(full_payload.data() + reticulum::kTruncatedHashSize, payload, payload_len); if (!unpackMessageEnvelope(full_payload.data(), full_payload.size(), out_envelope)) @@ -770,105 +762,6 @@ void copyCString(char* out, size_t out_len, const char* in) out[copy_len] = '\0'; } -bool copyTextAppDataDisplayName(const uint8_t* data, - size_t len, - char* out, - size_t out_len) -{ - if (!data || len == 0 || len > 96 || !out || out_len == 0) - { - return false; - } - - size_t used = 0; - bool has_visible = false; - for (size_t index = 0; index < len; ++index) - { - uint8_t byte = data[index]; - if (byte == '\t' || byte == '\r' || byte == '\n') - { - byte = ' '; - } - else if (byte == 0 || byte < 0x20 || byte == 0x7F) - { - out[0] = '\0'; - return false; - } - - if (used + 1U < out_len) - { - out[used++] = static_cast(byte); - } - if (byte != ' ') - { - has_visible = true; - } - } - while (used != 0 && out[used - 1U] == ' ') - { - --used; - } - out[used] = '\0'; - return has_visible && used != 0; -} - -bool isLxmfDeliveryAnnounce(const reticulum::ParsedAnnounce& announce) -{ - if (!announce.valid || !announce.name_hash) - { - return false; - } - - uint8_t expected_name_hash[reticulum::kNameHashSize] = {}; - reticulum::computeNameHash("lxmf", "delivery", expected_name_hash); - return hashesEqual(expected_name_hash, announce.name_hash, sizeof(expected_name_hash)); -} - -bool isLxmfPropagationAnnounce(const reticulum::ParsedAnnounce& announce) -{ - if (!announce.valid || !announce.name_hash) - { - return false; - } - - uint8_t expected_name_hash[reticulum::kNameHashSize] = {}; - reticulum::computeNameHash("lxmf", "propagation", expected_name_hash); - return hashesEqual(expected_name_hash, announce.name_hash, sizeof(expected_name_hash)); -} - -bool isCallAudioAnnounce(const reticulum::ParsedAnnounce& announce) -{ - if (!announce.valid || !announce.name_hash) - { - return false; - } - - uint8_t meshchat_name_hash[reticulum::kNameHashSize] = {}; - uint8_t lxst_name_hash[reticulum::kNameHashSize] = {}; - reticulum::computeNameHash("call", "audio", meshchat_name_hash); - reticulum::computeNameHash("lxst", "telephony", lxst_name_hash); - return hashesEqual(meshchat_name_hash, - announce.name_hash, - sizeof(meshchat_name_hash)) || - hashesEqual(lxst_name_hash, - announce.name_hash, - sizeof(lxst_name_hash)); -} - -bool isLxstTelephonyAnnounce(const reticulum::ParsedAnnounce& announce) -{ - if (!announce.valid || !announce.name_hash) - { - return false; - } - - uint8_t expected_name_hash[reticulum::kNameHashSize] = {}; - reticulum::computeNameHash("lxst", "telephony", expected_name_hash); - return hashesEqual(expected_name_hash, - announce.name_hash, - sizeof(expected_name_hash)); -} - bool isNomadNetworkNodeAnnounce(const reticulum::ParsedAnnounce& announce) { if (!announce.valid || !announce.name_hash) @@ -1179,6 +1072,15 @@ void LxmfAdapter::operator delete(void* ptr, std::size_t) noexcept operator delete(ptr); } +bool LxmfAdapter::resolveLocalDestinationForAnnounce( + void* context, + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + LocalDestinationKind* out_kind) +{ + auto* adapter = static_cast(context); + return adapter && adapter->isLocalDestinationHash(destination_hash, out_kind); +} + MeshCapabilities LxmfAdapter::getCapabilities() const { MeshCapabilities caps; @@ -1221,7 +1123,7 @@ bool LxmfAdapter::dispatchLxmfPayload(PeerInfo& peer, (void)sendPathRequest(peer); } - std::vector signed_part(kSignedPartMaxLen, 0); + runtime::RuntimeByteBuffer signed_part(kSignedPartMaxLen, 0); size_t signed_part_len = signed_part.size(); if (!buildSignedPart(peer.destination_hash, identity_.destinationHash(), @@ -1236,7 +1138,7 @@ bool LxmfAdapter::dispatchLxmfPayload(PeerInfo& peer, } uint8_t signature[reticulum::kSignatureSize] = {}; - std::vector lxmf_message(kMaxLxmfMessageLen, 0); + runtime::RuntimeByteBuffer lxmf_message(kMaxLxmfMessageLen, 0); size_t lxmf_message_len = lxmf_message.size(); if (!identity_.sign(signed_part.data(), signed_part_len, signature) || !packMessage(peer.destination_hash, @@ -1302,7 +1204,7 @@ bool LxmfAdapter::dispatchLxmfPayload(PeerInfo& peer, } else if (use_opportunistic) { - std::vector packet(kMaxPacketLen, 0); + runtime::RuntimeByteBuffer packet(kMaxPacketLen, 0); size_t packet_len = packet.size(); out_dispatch->ok = buildSignedMessagePacket(peer, @@ -1316,8 +1218,7 @@ bool LxmfAdapter::dispatchLxmfPayload(PeerInfo& peer, { uint8_t packet_hash[reticulum::kFullHashSize] = {}; reticulum::computePacketHash(packet.data(), packet_len, packet_hash); - runtime::notePendingDeliveryReceipt( - transport_, + path_manager_.notePendingDeliveryReceipt( packet_hash, peer.destination_hash, peer.sig_pub, @@ -1363,22 +1264,20 @@ LxmfAdapter::selectActivePropagationPeer() const auto& config = rtnet::active().propagation; if (!config.enabled) { - propagation_.has_active_node = false; - std::memset(propagation_.active_node_hash, - 0, - sizeof(propagation_.active_node_hash)); + propagation_client_.clearActivePeer(); return nullptr; } - const PropagationPeerState* selected = - runtime::selectPropagationPeer(propagation_, - config.automatic_node, - config.node_hash, - currentTimestampSeconds(), - kPropagationEntryTtlS); + const uint32_t now_s = currentTimestampSeconds(); + const runtime::PropagationActivePeerSelection selection = + propagation_client_.selectActivePeer(config.automatic_node, + config.node_hash, + now_s, + kPropagationEntryTtlS, + config.sync_on_start); + const PropagationPeerState* selected = selection.peer; if (!selected) { - propagation_.has_active_node = false; if (!config.automatic_node) { (void)sendPathRequestForDestination(config.node_hash); @@ -1386,16 +1285,7 @@ LxmfAdapter::selectActivePropagationPeer() return nullptr; } - const bool changed = - !propagation_.has_active_node || - !hashesEqual(propagation_.active_node_hash, - selected->propagation_hash, - sizeof(propagation_.active_node_hash)); - copyHash(propagation_.active_node_hash, - selected->propagation_hash, - sizeof(propagation_.active_node_hash)); - propagation_.has_active_node = true; - if (changed) + if (selection.changed) { char node_hash[12] = {}; formatHashPrefix(selected->propagation_hash, @@ -1406,12 +1296,6 @@ LxmfAdapter::selectActivePropagationPeer() static_cast(selected->hops), static_cast(selected->stamp_cost), config.automatic_node ? "auto" : "manual"); - propagation_.initial_sync_pending = config.sync_on_start; - if (!config.sync_on_start) - { - propagation_.last_sync_s = currentTimestampSeconds(); - } - propagation_.sync_stage = PropagationSyncStage::Idle; } return selected; } @@ -1459,8 +1343,7 @@ bool LxmfAdapter::queuePropagationUpload( if (!lxmf_message || lxmf_message_len <= reticulum::kTruncatedHashSize || !message_hash || !out_dispatch || - propagation_.pending_uploads.size() >= - kMaxPendingPropagationUploads) + !propagation_client_.canQueueUpload(kMaxPendingPropagationUploads)) { return false; } @@ -1469,7 +1352,7 @@ bool LxmfAdapter::queuePropagationUpload( LxmfIdentity::kEncPubKeySize + reticulum::tokenSizeForPlaintext( lxmf_message_len - reticulum::kTruncatedHashSize); - std::vector encrypted(encrypted_capacity, 0); + runtime::RuntimeByteBuffer encrypted(encrypted_capacity, 0); size_t encrypted_len = encrypted.size(); if (!encryptForPeer(recipient, lxmf_message + reticulum::kTruncatedHashSize, @@ -1511,20 +1394,31 @@ bool LxmfAdapter::queuePropagationUpload( upload.state = runtime::PropagationUploadState::NeedsStamp; } - propagation_.pending_uploads.push_back(std::move(upload)); + PendingPropagationUpload* queued_upload = + propagation_client_.queueUpload(std::move(upload), + kMaxPendingPropagationUploads); + if (!queued_upload) + { + return false; + } out_dispatch->ok = true; out_dispatch->result_event_deferred = track_user_message; out_dispatch->path = "propagation"; + if (track_user_message && message_id != 0) + { + sys::EventBus::publish( + new sys::ChatSendResultEvent(message_id, MessageStatus::Queued), + 0); + } char transient_hash[12] = {}; - formatHashPrefix(propagation_.pending_uploads.back().transient_id, + formatHashPrefix(queued_upload->transient_id, transient_hash, sizeof(transient_hash)); Serial.printf("[LXMF][PropagationTX] queued msg=%lu transient=%s bytes=%u node=%u\n", static_cast(message_id), transient_hash, - static_cast( - propagation_.pending_uploads.back().transient_data.size()), - propagation_.pending_uploads.back().state == + static_cast(queued_upload->transient_data.size()), + queued_upload->state == runtime::PropagationUploadState::NeedsStamp ? 1U : 0U); @@ -1537,13 +1431,13 @@ bool LxmfAdapter::queueReadyPropagationUpload( { if (upload.state != runtime::PropagationUploadState::Ready || upload.transient_data.empty() || - !preparePropagationPeer(node, &propagation_peer_scratch_)) + !preparePropagationPeer(node, &propagation_client_.peerScratch())) { return false; } bool started = false; - LinkSession* session = ensureOutboundLinkSession(propagation_peer_scratch_, + LinkSession* session = ensureOutboundLinkSession(propagation_client_.peerScratch(), LocalDestinationKind::Propagation, &started); if (!session) @@ -1596,7 +1490,9 @@ void LxmfAdapter::processPropagationClient() const auto& config = rtnet::active().propagation; if (!config.enabled) { - for (const auto& upload : propagation_.pending_uploads) + const std::vector disabled_uploads = + propagation_client_.takeAllPendingUploads(); + for (const auto& upload : disabled_uploads) { if (upload.track_user_message && upload.message_id != 0) { @@ -1607,72 +1503,49 @@ void LxmfAdapter::processPropagationClient() Serial.printf("[LXMF][PropagationTX] disabled msg=%lu\n", static_cast(upload.message_id)); } - propagation_.pending_uploads.clear(); - propagation_.sync_wants.clear(); - propagation_.sync_haves.clear(); - runtime::clearPropagationDeliveryCommits(propagation_); - propagation_.persistence_started_ms = 0; - propagation_.sync_stage = PropagationSyncStage::Idle; - propagation_.has_active_node = false; - propagation_.initial_sync_pending = true; - std::memset(propagation_.active_node_hash, - 0, - sizeof(propagation_.active_node_hash)); - std::memset(propagation_.sync_request_id, - 0, - sizeof(propagation_.sync_request_id)); - for (auto& session : links_.sessions) - { - if (session.destination == LocalDestinationKind::Propagation && - session.state != LinkState::Closed) + propagation_client_.resetForDisabled(); + link_manager_.forEachSession( + [this](LinkSession& session) { - closeLinkSession(session, LinkCloseReason::LocalClose); - } - } - propagation_stamp_.reset(); + if (session.destination == LocalDestinationKind::Propagation && + session.state != LinkState::Closed) + { + closeLinkSession(session, LinkCloseReason::LocalClose); + } + }); return; } const uint32_t now_ms = millis(); - for (auto& upload : propagation_.pending_uploads) + propagation_client_.markExpiredUploads(now_ms, kPropagationUploadTtlMs); + const std::vector failed_uploads = + propagation_client_.takeFailedUploads(); + for (const auto& upload : failed_uploads) { - if (upload.state != runtime::PropagationUploadState::Failed && - upload.created_ms != 0 && - (now_ms - upload.created_ms) > kPropagationUploadTtlMs) + if (upload.track_user_message && upload.message_id != 0) { - upload.state = runtime::PropagationUploadState::Failed; + sys::EventBus::publish( + new sys::ChatSendResultEvent(upload.message_id, false), + 0); } + Serial.printf("[LXMF][PropagationTX] failed msg=%lu\n", + static_cast(upload.message_id)); } - propagation_.pending_uploads.erase( - std::remove_if( - propagation_.pending_uploads.begin(), - propagation_.pending_uploads.end(), - [](const PendingPropagationUpload& upload) - { - if (upload.state != runtime::PropagationUploadState::Failed) - { - return false; - } - if (upload.track_user_message && upload.message_id != 0) - { - sys::EventBus::publish( - new sys::ChatSendResultEvent(upload.message_id, false), - 0); - } - Serial.printf("[LXMF][PropagationTX] failed msg=%lu\n", - static_cast(upload.message_id)); - return true; - }), - propagation_.pending_uploads.end()); const PropagationPeerState* node = selectActivePropagationPeer(); - if (!propagation_.pending_uploads.empty()) + if (propagation_client_.hasPendingUploads()) { - PendingPropagationUpload& upload = propagation_.pending_uploads.front(); + PendingPropagationUpload* pending_upload = + propagation_client_.firstPendingUpload(); + if (!pending_upload) + { + return; + } + PendingPropagationUpload& upload = *pending_upload; if (!node) { upload.state = runtime::PropagationUploadState::WaitingNode; - propagation_stamp_.reset(); + propagation_client_.stamp().reset(); } else { @@ -1683,7 +1556,7 @@ void LxmfAdapter::processPropagationClient() upload.stamp_cost != node->stamp_cost; if (node_changed) { - propagation_stamp_.reset(); + propagation_client_.stamp().reset(); copyHash(upload.node_hash, node->propagation_hash, sizeof(upload.node_hash)); @@ -1698,8 +1571,8 @@ void LxmfAdapter::processPropagationClient() if (upload.state == runtime::PropagationUploadState::NeedsStamp) { - if (propagation_stamp_.begin(upload.transient_id, - upload.stamp_cost)) + if (propagation_client_.stamp().begin(upload.transient_id, + upload.stamp_cost)) { upload.state = runtime::PropagationUploadState::Stamping; Serial.printf("[LXMF][PropagationTX] stamp_begin msg=%lu cost=%u\n", @@ -1714,14 +1587,14 @@ void LxmfAdapter::processPropagationClient() if (upload.state == runtime::PropagationUploadState::Stamping) { - const auto stamp_state = propagation_stamp_.poll(); + const auto stamp_state = propagation_client_.stamp().poll(); if (stamp_state == runtime::PropagationStampRuntime::State::Complete) { uint8_t stamp[reticulum::kFullHashSize] = {}; const uint32_t rounds = - propagation_stamp_.searchRounds(); - if (!propagation_stamp_.takeStamp(stamp)) + propagation_client_.stamp().searchRounds(); + if (!propagation_client_.stamp().takeStamp(stamp)) { upload.state = runtime::PropagationUploadState::Failed; @@ -1745,35 +1618,34 @@ void LxmfAdapter::processPropagationClient() } else if (stamp_state == runtime::PropagationStampRuntime::State::Expanding && - propagation_stamp_.expandedRounds() != 0U && - (propagation_stamp_.expandedRounds() % 100U) == 0U) + propagation_client_.stamp().expandedRounds() != 0U && + (propagation_client_.stamp().expandedRounds() % 100U) == 0U) { Serial.printf("[LXMF][PropagationTX] stamp_progress msg=%lu expand=%u/1000 search=%lu\n", static_cast(upload.message_id), static_cast( - propagation_stamp_.expandedRounds()), + propagation_client_.stamp().expandedRounds()), static_cast( - propagation_stamp_.searchRounds())); + propagation_client_.stamp().searchRounds())); } else if (stamp_state == runtime::PropagationStampRuntime::State::Searching && - propagation_stamp_.searchRounds() != 0U && - (propagation_stamp_.searchRounds() % 4096U) == 0U) + propagation_client_.stamp().searchRounds() != 0U && + (propagation_client_.stamp().searchRounds() % 4096U) == 0U) { Serial.printf("[LXMF][PropagationTX] stamp_progress msg=%lu expand=%u/1000 search=%lu\n", static_cast(upload.message_id), static_cast( - propagation_stamp_.expandedRounds()), + propagation_client_.stamp().expandedRounds()), static_cast( - propagation_stamp_.searchRounds())); + propagation_client_.stamp().searchRounds())); } } if (upload.state == runtime::PropagationUploadState::Ready && queueReadyPropagationUpload(upload, *node)) { - propagation_.pending_uploads.erase( - propagation_.pending_uploads.begin()); + propagation_client_.removeFirstPendingUpload(); } } } @@ -1781,23 +1653,17 @@ void LxmfAdapter::processPropagationClient() if (node) { LinkSession* session = - runtime::findOpenLinkSessionByDestination( - links_, + link_manager_.findOpenSessionByDestination( node->propagation_hash, LocalDestinationKind::Propagation); const auto& sync_config = rtnet::active().propagation; const uint32_t now_s = currentTimestampSeconds(); const bool sync_due = - propagation_.initial_sync_pending || - (sync_config.sync_interval_s != 0 && - (propagation_.last_sync_s == 0 || - now_s < propagation_.last_sync_s || - (now_s - propagation_.last_sync_s) >= - sync_config.sync_interval_s)); + propagation_client_.syncDue(now_s, sync_config.sync_interval_s); if (!session && sync_due && - preparePropagationPeer(*node, &propagation_peer_scratch_)) + preparePropagationPeer(*node, &propagation_client_.peerScratch())) { - session = ensureOutboundLinkSession(propagation_peer_scratch_, + session = ensureOutboundLinkSession(propagation_client_.peerScratch(), LocalDestinationKind::Propagation, nullptr); } @@ -1811,8 +1677,8 @@ void LxmfAdapter::processPropagationClient() bool LxmfAdapter::sendPropagationSyncRequest( LinkSession& session, PropagationSyncStage next_stage, - const std::vector>* wants, - const std::vector>* haves, + const runtime::PropagationIdList* wants, + const runtime::PropagationIdList* haves, bool include_transfer_limit) { if (session.state != LinkState::Active || @@ -1823,14 +1689,22 @@ bool LxmfAdapter::sendPropagationSyncRequest( const size_t item_count = (wants ? wants->size() : 0U) + (haves ? haves->size() : 0U); - std::vector packed_data(32U + item_count * 35U, 0); + runtime::ResourcePayloadBuffer packed_data(32U + item_count * 35U, 0); size_t packed_data_len = packed_data.size(); - if (!encodePropagationGetRequestPayload(wants, - haves, - include_transfer_limit, - kPropagationTransferLimitKb, - packed_data.data(), - &packed_data_len)) + const runtime::RuntimeByteSpanList want_spans = + wants ? runtime::makeRuntimeByteSpans(*wants) + : runtime::RuntimeByteSpanList{}; + const runtime::RuntimeByteSpanList have_spans = + haves ? runtime::makeRuntimeByteSpans(*haves) + : runtime::RuntimeByteSpanList{}; + const ByteSpanList want_view = runtime::viewRuntimeByteSpans(want_spans); + const ByteSpanList have_view = runtime::viewRuntimeByteSpans(have_spans); + if (!encodePropagationGetRequestPayloadSpans(wants ? &want_view : nullptr, + haves ? &have_view : nullptr, + include_transfer_limit, + kPropagationTransferLimitKb, + packed_data.data(), + &packed_data_len)) { return false; } @@ -1840,7 +1714,7 @@ bool LxmfAdapter::sendPropagationSyncRequest( runtime::propagationServicePathHash( runtime::PropagationServicePath::Get, path_hash); - std::vector request_payload(packed_data.size() + 64U, 0); + runtime::ResourcePayloadBuffer request_payload(packed_data.size() + 64U, 0); size_t request_payload_len = request_payload.size(); if (!encodeLinkRequestPayload( static_cast(currentTimestampSeconds()), @@ -1859,7 +1733,7 @@ bool LxmfAdapter::sendPropagationSyncRequest( bool sent = false; if (request_payload.size() <= session.mdu) { - std::vector wire_payload( + runtime::ResourcePayloadBuffer wire_payload( reticulum::tokenSizeForPlaintext(request_payload.size()), 0); size_t wire_payload_len = wire_payload.size(); @@ -1917,10 +1791,7 @@ bool LxmfAdapter::sendPropagationSyncRequest( pending.created_ms = millis(); pending.awaiting_resource = request_payload.size() > session.mdu; session.pending_requests.push_back(std::move(pending)); - copyHash(propagation_.sync_request_id, - request_id, - sizeof(propagation_.sync_request_id)); - propagation_.sync_stage = next_stage; + propagation_client_.markSyncRequestSent(request_id, next_stage); session.last_outbound_ms = millis(); Serial.printf("[LXMF][PropagationSync] request stage=%u wants=%u haves=%u resource=%u\n", static_cast(next_stage), @@ -1940,22 +1811,9 @@ void LxmfAdapter::processPropagationSyncResponse(LinkSession& session) const auto& config = rtnet::active().propagation; const uint32_t now_s = currentTimestampSeconds(); - const bool interval_due = - config.sync_interval_s != 0 && - (propagation_.last_sync_s == 0 || now_s < propagation_.last_sync_s || - (now_s - propagation_.last_sync_s) >= config.sync_interval_s); - if (propagation_.sync_stage == PropagationSyncStage::Idle && - (propagation_.initial_sync_pending || interval_due)) - { - propagation_.sync_stage = PropagationSyncStage::NeedList; - propagation_.sync_started_ms = millis(); - propagation_.persistence_started_ms = 0; - propagation_.sync_wants.clear(); - propagation_.sync_haves.clear(); - runtime::clearPropagationDeliveryCommits(propagation_); - } + propagation_client_.startSyncIfDue(now_s, millis(), config.sync_interval_s); - if (propagation_.sync_stage == PropagationSyncStage::NeedList) + if (propagation_client_.syncStage() == PropagationSyncStage::NeedList) { (void)sendLinkIdentify(session); if (!sendPropagationSyncRequest(session, @@ -1964,7 +1822,7 @@ void LxmfAdapter::processPropagationSyncResponse(LinkSession& session) nullptr, false)) { - propagation_.sync_stage = PropagationSyncStage::Failed; + propagation_client_.markSyncFailed(); } return; } @@ -1973,89 +1831,66 @@ void LxmfAdapter::processPropagationSyncResponse(LinkSession& session) session.pending_requests.begin(), session.pending_requests.end(), [this](const LinkPendingRequest& request) - { - return request.request_id.size() == - sizeof(propagation_.sync_request_id) && - hashesEqual(request.request_id.data(), - propagation_.sync_request_id, - sizeof(propagation_.sync_request_id)); - }); - if ((propagation_.sync_stage == PropagationSyncStage::Listing || - propagation_.sync_stage == PropagationSyncStage::Downloading || - propagation_.sync_stage == PropagationSyncStage::Acknowledging) && + { return propagation_client_.syncRequestMatches(request); }); + if ((propagation_client_.syncStage() == PropagationSyncStage::Listing || + propagation_client_.syncStage() == PropagationSyncStage::Downloading || + propagation_client_.syncStage() == PropagationSyncStage::Acknowledging) && pending != session.pending_requests.end() && pending->created_ms != 0 && (millis() - pending->created_ms) > kLinkRequestTtlMs) { session.pending_requests.erase(pending); - propagation_.sync_stage = PropagationSyncStage::Failed; + propagation_client_.markSyncFailed(); pending = session.pending_requests.end(); } - if (propagation_.sync_stage == PropagationSyncStage::Listing && + if (propagation_client_.syncStage() == PropagationSyncStage::Listing && pending != session.pending_requests.end() && pending->response_ready) { - std::vector> remote_ids; + runtime::PropagationIdList remote_ids; const bool decoded = decodePropagationIdListPayload(pending->response.data(), pending->response.size(), + runtime::appendRuntimeByteBufferCallback, &remote_ids); session.pending_requests.erase(pending); if (!decoded) { - propagation_.sync_stage = PropagationSyncStage::Failed; + propagation_client_.markSyncFailed(); } else { - const size_t max_messages = - std::max(1U, config.max_messages_per_sync); - for (const auto& transient_id : remote_ids) - { - if (transient_id.size() != reticulum::kFullHashSize) - { - continue; - } - if (runtime::hasSeenPropagationTransient( - propagation_, transient_id.data(), nullptr)) - { - propagation_.sync_haves.push_back(transient_id); - } - else if (propagation_.sync_wants.size() < max_messages) - { - propagation_.sync_wants.push_back(transient_id); - } - } - propagation_.sync_stage = propagation_.sync_wants.empty() - ? PropagationSyncStage::NeedAcknowledge - : PropagationSyncStage::NeedMessages; + propagation_client_.noteListingResult(remote_ids, + config.max_messages_per_sync); } } - if (propagation_.sync_stage == PropagationSyncStage::NeedMessages) + if (propagation_client_.syncStage() == PropagationSyncStage::NeedMessages) { if (!sendPropagationSyncRequest(session, PropagationSyncStage::Downloading, - &propagation_.sync_wants, - &propagation_.sync_haves, + &propagation_client_.syncWants(), + &propagation_client_.syncHaves(), true)) { - propagation_.sync_stage = PropagationSyncStage::Failed; + propagation_client_.markSyncFailed(); } return; } - if (propagation_.sync_stage == PropagationSyncStage::Downloading && + if (propagation_client_.syncStage() == PropagationSyncStage::Downloading && pending != session.pending_requests.end() && pending->response_ready) { - std::vector> messages; + runtime::PropagationMessageList messages; const bool decoded = decodePropagationMessageListPayload(pending->response.data(), pending->response.size(), + runtime::appendRuntimeByteBufferCallback, &messages); session.pending_requests.erase(pending); if (!decoded) { - propagation_.sync_stage = PropagationSyncStage::Failed; + propagation_client_.markSyncFailed(); } else { @@ -2086,12 +1921,10 @@ void LxmfAdapter::processPropagationSyncResponse(LinkSession& session) { if (awaiting_commit) { - if (!runtime::awaitPropagationDeliveryCommit( - propagation_, + if (!propagation_client_.registerDeliveryCommit( transient_id, message_hash, - std::max(1U, - config.max_messages_per_sync))) + config.max_messages_per_sync)) { delivery_commit_registration_failed = true; break; @@ -2099,108 +1932,72 @@ void LxmfAdapter::processPropagationSyncResponse(LinkSession& session) } else { - runtime::rememberPropagationTransient( - propagation_, + propagation_client_.rememberDeliveredTransient( transient_id, - true, currentTimestampSeconds(), kMaxPropagationTransients); - propagation_.sync_haves.emplace_back( - transient_id, - transient_id + sizeof(transient_id)); } } } - propagation_.sync_stage = - delivery_commit_registration_failed - ? PropagationSyncStage::Failed - : (propagation_.pending_deliveries.empty() - ? PropagationSyncStage::NeedAcknowledge - : PropagationSyncStage::AwaitingPersistence); - if (propagation_.sync_stage == - PropagationSyncStage::AwaitingPersistence) - { - propagation_.persistence_started_ms = millis(); - } + propagation_client_.noteDownloadResult( + delivery_commit_registration_failed, + millis()); } } - if (propagation_.sync_stage == PropagationSyncStage::AwaitingPersistence) + if (propagation_client_.syncStage() == PropagationSyncStage::AwaitingPersistence) { - if (propagation_.persistence_started_ms == 0 || - (millis() - propagation_.persistence_started_ms) > - kLinkRequestTtlMs) - { - propagation_.sync_stage = PropagationSyncStage::Failed; - } - else if (runtime::propagationDeliveryCommitsResolved(propagation_)) - { - propagation_.sync_stage = - runtime::propagationDeliveryCommitRejected(propagation_) - ? PropagationSyncStage::Failed - : PropagationSyncStage::NeedAcknowledge; - } - else + if (!propagation_client_.pollPersistence(millis(), kLinkRequestTtlMs)) { return; } } - if (propagation_.sync_stage == PropagationSyncStage::NeedAcknowledge) + if (propagation_client_.syncStage() == PropagationSyncStage::NeedAcknowledge) { const bool acknowledged = - propagation_.sync_haves.empty() || + propagation_client_.syncHavesEmpty() || sendPropagationSyncRequest(session, PropagationSyncStage::Acknowledging, nullptr, - &propagation_.sync_haves, + &propagation_client_.syncHaves(), false); if (acknowledged) { - propagation_.sync_stage = propagation_.sync_haves.empty() - ? PropagationSyncStage::Complete - : PropagationSyncStage::Acknowledging; + if (propagation_client_.syncHavesEmpty()) + { + propagation_client_.markAcknowledged(); + } } else { - propagation_.sync_stage = PropagationSyncStage::Failed; + propagation_client_.markSyncFailed(); } - if (propagation_.sync_stage == PropagationSyncStage::Acknowledging) + if (propagation_client_.syncStage() == PropagationSyncStage::Acknowledging) { return; } } - if (propagation_.sync_stage == PropagationSyncStage::Acknowledging && + if (propagation_client_.syncStage() == PropagationSyncStage::Acknowledging && pending != session.pending_requests.end() && pending->response_ready) { session.pending_requests.erase(pending); - propagation_.sync_stage = PropagationSyncStage::Complete; + propagation_client_.markAcknowledged(); } - if (propagation_.sync_stage == PropagationSyncStage::Complete) + if (propagation_client_.syncStage() == PropagationSyncStage::Complete) { - propagation_.last_sync_s = now_s; - propagation_.initial_sync_pending = false; + const std::size_t acknowledged_count = propagation_client_.syncHaveCount(); Serial.printf("[LXMF][PropagationSync] complete received=%u acknowledged=%u\n", - static_cast(propagation_.sync_haves.size()), - static_cast(propagation_.sync_haves.size())); - propagation_.sync_wants.clear(); - propagation_.sync_haves.clear(); - runtime::clearPropagationDeliveryCommits(propagation_); - propagation_.persistence_started_ms = 0; - propagation_.sync_stage = PropagationSyncStage::Idle; + static_cast(acknowledged_count), + static_cast(acknowledged_count)); + propagation_client_.finishSyncComplete(now_s); } - else if (propagation_.sync_stage == PropagationSyncStage::Failed) + else if (propagation_client_.syncStage() == PropagationSyncStage::Failed) { Serial.println("[LXMF][PropagationSync] failed"); - propagation_.sync_wants.clear(); - propagation_.sync_haves.clear(); - runtime::clearPropagationDeliveryCommits(propagation_); - propagation_.persistence_started_ms = 0; - propagation_.sync_stage = PropagationSyncStage::Idle; - propagation_.initial_sync_pending = false; - propagation_.last_sync_s = now_s; + propagation_client_.finishSyncFailed(now_s); } } @@ -2231,7 +2028,7 @@ bool LxmfAdapter::respondToSidebandTelemetryRequest( location.accuracy_cm = 0; location.timestamp = currentTimestampSeconds(); - std::vector packed_payload(kMaxLxmfMessageLen, 0); + runtime::RuntimeByteBuffer packed_payload(kMaxLxmfMessageLen, 0); size_t packed_payload_len = packed_payload.size(); if (!encodeSidebandTelemetryLocationPayload( static_cast(location.timestamp), @@ -2304,7 +2101,7 @@ MeshSendResult LxmfAdapter::sendTextDetailed(ChannelId channel, peer_dest_full, sizeof(peer_dest_full)); - std::vector packed_payload(kMaxLxmfMessageLen, 0); + runtime::RuntimeByteBuffer packed_payload(kMaxLxmfMessageLen, 0); size_t packed_payload_len = packed_payload.size(); if (!encodeTextPayload(static_cast(currentTimestampSeconds()), "", @@ -2349,7 +2146,11 @@ MeshSendResult LxmfAdapter::sendTextDetailed(ChannelId channel, result.reticulum_identity = reticulumIdentityForPeer(*peer_info); if (!send_result_event_deferred) { - sys::EventBus::publish(new sys::ChatSendResultEvent(message_id, ok), 0); + sys::EventBus::publish( + new sys::ChatSendResultEvent( + message_id, + ok ? MessageStatus::Queued : MessageStatus::Failed), + 0); } return result; } @@ -2484,7 +2285,11 @@ MeshSendResult LxmfAdapter::sendTextToReticulumDestination( : MeshSendResult::fail(MeshOperationFailure::RadioTxFailed, message_id); result.reticulum_identity = makeReticulumDestinationIdentity(destination.destination_hash); - sys::EventBus::publish(new sys::ChatSendResultEvent(message_id, ok), 0); + sys::EventBus::publish( + new sys::ChatSendResultEvent( + message_id, + ok ? MessageStatus::Queued : MessageStatus::Failed), + 0); return result; } @@ -2501,11 +2306,10 @@ void LxmfAdapter::commitIncomingText(const MeshIncomingText& message, return; } - if (!runtime::commitPropagationDelivery(propagation_, - message.reticulum_lxmf_hash, - durably_accepted, - currentTimestampSeconds(), - kMaxPropagationTransients)) + if (!propagation_client_.noteDeliveryCommit(message.reticulum_lxmf_hash, + durably_accepted, + currentTimestampSeconds(), + kMaxPropagationTransients)) { return; } @@ -2513,16 +2317,8 @@ void LxmfAdapter::commitIncomingText(const MeshIncomingText& message, Serial.printf("[LXMF][PropagationSync] durable_commit msg=%08lX accepted=%u pending=%u\n", static_cast(message.msg_id), durably_accepted ? 1U : 0U, - static_cast(propagation_.pending_deliveries.size())); - - if (propagation_.sync_stage == PropagationSyncStage::AwaitingPersistence && - runtime::propagationDeliveryCommitsResolved(propagation_)) - { - propagation_.sync_stage = - runtime::propagationDeliveryCommitRejected(propagation_) - ? PropagationSyncStage::Failed - : PropagationSyncStage::NeedAcknowledge; - } + static_cast( + propagation_client_.pendingDeliveryCount())); } bool LxmfAdapter::sendAppData(ChannelId channel, uint32_t portnum, @@ -2699,34 +2495,36 @@ bool LxmfAdapter::sendAppData(ChannelId channel, uint32_t portnum, ok = true; // LXMF delivery is single-destination. For device-side team/business // traffic we currently treat dest==0 as fan-out to every known peer. - for (auto& peer_info : peers_) - { - if (isZeroBytes(peer_info.destination_hash, sizeof(peer_info.destination_hash))) + destination_registry_.forEach( + [&](PeerInfo& peer_info) { - continue; - } + if (isZeroBytes(peer_info.destination_hash, + sizeof(peer_info.destination_hash))) + { + return; + } - have_peer = true; - ++fanout_count; - if (shouldRequestPath(peer_info)) - { - (void)sendPathRequest(peer_info); - } + have_peer = true; + ++fanout_count; + if (shouldRequestPath(peer_info)) + { + (void)sendPathRequest(peer_info); + } - uint8_t packet[kMaxPacketLen] = {}; - size_t packet_len = sizeof(packet); - uint8_t message_hash[reticulum::kFullHashSize] = {}; - if (!buildSignedMessagePacket(peer_info, - packed_payload, - packed_payload_len, - packet, - &packet_len, - message_hash) || - !routeAndSendPacket(packet, packet_len, true)) - { - ok = false; - } - } + uint8_t packet[kMaxPacketLen] = {}; + size_t packet_len = sizeof(packet); + uint8_t message_hash[reticulum::kFullHashSize] = {}; + if (!buildSignedMessagePacket(peer_info, + packed_payload, + packed_payload_len, + packet, + &packet_len, + message_hash) || + !routeAndSendPacket(packet, packet_len, true)) + { + ok = false; + } + }); ok = have_peer && ok; Serial.printf("[LXMF][AppDataTX] fanout port=%lu msg=%lu peers=%u ok=%u\n", static_cast(portnum), @@ -2740,7 +2538,11 @@ bool LxmfAdapter::sendAppData(ChannelId channel, uint32_t portnum, static_cast(effective_packet_id), static_cast(dest), ok ? 1U : 0U); - sys::EventBus::publish(new sys::ChatSendResultEvent(effective_packet_id, ok), 0); + sys::EventBus::publish( + new sys::ChatSendResultEvent( + effective_packet_id, + ok ? MessageStatus::Queued : MessageStatus::Failed), + 0); return ok; } @@ -2891,8 +2693,8 @@ MeshActionResult LxmfAdapter::startReticulumAudioCall( return MeshActionResult::fail(MeshOperationFailure::EncodeFailed); } - if (runtime::findOpenLinkSessionByDestination( - links_, call_destination_hash, LocalDestinationKind::CallAudio)) + if (link_manager_.findOpenSessionByDestination( + call_destination_hash, LocalDestinationKind::CallAudio)) { return MeshActionResult::fail(MeshOperationFailure::Busy); } @@ -2913,7 +2715,12 @@ MeshActionResult LxmfAdapter::startReticulumAudioCall( } } - LinkSession& session = runtime::appendLinkSession(links_, kMaxLinkSessions); + LinkSession* new_session = link_manager_.appendSession(kMaxLinkSessions); + if (!new_session) + { + return MeshActionResult::fail(MeshOperationFailure::Busy); + } + LinkSession& session = *new_session; session.created_ms = millis(); session.request_ms = session.created_ms; session.last_inbound_ms = session.created_ms; @@ -2949,7 +2756,7 @@ MeshActionResult LxmfAdapter::startReticulumAudioCall( !generateLinkSigningKey(session.local_sig_pub, session.local_sig_priv) || !prepareLinkRequest(session)) { - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return MeshActionResult::fail(MeshOperationFailure::EncodeFailed); } @@ -2983,7 +2790,7 @@ MeshActionResult LxmfAdapter::startReticulumAudioCall( session.lxst_call.profile); if (!::platform::ui::reticulum_call::begin_outgoing(call_peer)) { - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return MeshActionResult::fail(MeshOperationFailure::Busy); } session.call_runtime_started = true; @@ -2991,7 +2798,7 @@ MeshActionResult LxmfAdapter::startReticulumAudioCall( if ((path && !sendLinkRequest(session)) || (!path && !path_waiting)) { ::platform::ui::reticulum_call::notify_link_closed(session.link_id); - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return MeshActionResult::fail(MeshOperationFailure::RadioTxFailed); } @@ -3088,7 +2895,7 @@ MeshActionResult LxmfAdapter::sendReticulumPingToPeer( requested ? 1U : 0U); } - std::vector packet(kMaxPacketLen, 0); + runtime::RuntimeByteBuffer packet(kMaxPacketLen, 0); size_t packet_len = packet.size(); if (!buildEncryptedPacketForPeer(peer, nullptr, 0, packet.data(), &packet_len)) { @@ -3102,13 +2909,13 @@ MeshActionResult LxmfAdapter::sendReticulumPingToPeer( { uint8_t packet_hash[reticulum::kFullHashSize] = {}; reticulum::computePacketHash(packet.data(), packet_len, packet_hash); - runtime::notePendingPingReceipt(transport_, - packet_hash, - peer.destination_hash, - peer.sig_pub, - operation_started_ms == 0 ? millis() - : operation_started_ms, - kMaxPendingPingReceipts); + path_manager_.notePendingPingReceipt( + packet_hash, + peer.destination_hash, + peer.sig_pub, + operation_started_ms == 0 ? millis() + : operation_started_ms, + kMaxPendingPingReceipts); } const auto& tx_result = interfaces_.lastTxResult(); Serial.printf("[LXMF][PingTX] raw_send ok=%u dest=%s dest_full=%s bearer=%s complete=%u receipt=%u packet_len=%u\n", @@ -3128,41 +2935,33 @@ MeshActionResult LxmfAdapter::queuePendingReticulumPing( { char dest_hash[12] = {}; formatHashPrefix(destination_hash, dest_hash, sizeof(dest_hash)); - for (const PendingPingRequest& pending : pending_ping_requests_) + const runtime::PendingPingQueueResult queue_result = + ping_service_.queue(destination_hash, millis(), kMaxPendingPingRequests); + if (queue_result == runtime::PendingPingQueueResult::Duplicate) { - if (hashesEqual(pending.destination_hash, - destination_hash, - sizeof(pending.destination_hash))) - { - Serial.printf("[LXMF][PingTX] path_pending dest=%s queued=1 duplicate=1\n", - dest_hash); - MeshActionResult result = MeshActionResult::success(); - result.detail = 1; - return result; - } + Serial.printf("[LXMF][PingTX] path_pending dest=%s queued=1 duplicate=1\n", + dest_hash); + MeshActionResult result = MeshActionResult::success(); + result.detail = 1; + return result; } - - if (pending_ping_requests_.size() >= kMaxPendingPingRequests) + if (queue_result == runtime::PendingPingQueueResult::Full) { Serial.printf("[LXMF][PingTX] reject reason=pending_full dest=%s depth=%u\n", dest_hash, - static_cast(pending_ping_requests_.size())); + static_cast(ping_service_.size())); return MeshActionResult::fail(MeshOperationFailure::Busy); } - - PendingPingRequest request{}; - copyHash(request.destination_hash, - destination_hash, - sizeof(request.destination_hash)); - request.created_ms = millis(); - request.last_path_request_ms = request.created_ms; - pending_ping_requests_.push_back(request); + if (queue_result != runtime::PendingPingQueueResult::Queued) + { + return MeshActionResult::fail(MeshOperationFailure::InvalidInput); + } const bool path_requested = sendPathRequestForDestination(destination_hash); Serial.printf("[LXMF][PingTX] path_pending dest=%s queued=1 requested=%u depth=%u\n", dest_hash, path_requested ? 1U : 0U, - static_cast(pending_ping_requests_.size())); + static_cast(ping_service_.size())); MeshActionResult result = MeshActionResult::success(); result.detail = 1; return result; @@ -3173,15 +2972,52 @@ void LxmfAdapter::pumpPendingPingRequests() const uint32_t now_ms = millis(); const bool call_preempt_active = ::platform::ui::reticulum_call::resource_preempt_active(); - for (std::size_t index = 0; index < pending_ping_requests_.size();) - { - PendingPingRequest& request = pending_ping_requests_[index]; - const uint32_t elapsed_ms = now_ms - request.created_ms; - char dest_hash[12] = {}; - formatHashPrefix(request.destination_hash, dest_hash, sizeof(dest_hash)); - - if (request.created_ms == 0 || elapsed_ms > kPendingPingReceiptTtlMs) + ping_service_.pump( + now_ms, + call_preempt_active, + kPendingPingReceiptTtlMs, + kPendingPingSendRetryMs, + kPathRequestMinIntervalMs, + [this](const uint8_t destination_hash[reticulum::kTruncatedHashSize]) { + PeerInfo* peer = findOrLoadPeerByDestinationHash(destination_hash); + return peer && + !isZeroBytes(peer->identity_hash, sizeof(peer->identity_hash)) && + !isZeroBytes(peer->sig_pub, sizeof(peer->sig_pub)) && + (peerHasUsableRatchet(*peer) || + !isZeroBytes(peer->enc_pub, sizeof(peer->enc_pub))); + }, + [this](const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t created_ms, + uint32_t elapsed_ms) + { + char dest_hash[12] = {}; + formatHashPrefix(destination_hash, dest_hash, sizeof(dest_hash)); + Serial.printf("[LXMF][PingTX] dispatch_after_path dest=%s elapsed_ms=%lu\n", + dest_hash, + static_cast(elapsed_ms)); + PeerInfo* peer = findOrLoadPeerByDestinationHash(destination_hash); + if (!peer) + { + return false; + } + return sendReticulumPingToPeer(*peer, created_ms).ok; + }, + [this](const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t elapsed_ms) + { + const bool requested = sendPathRequestForDestination(destination_hash); + char dest_hash[12] = {}; + formatHashPrefix(destination_hash, dest_hash, sizeof(dest_hash)); + Serial.printf("[LXMF][PingTX] path_retry dest=%s requested=%u elapsed_ms=%lu\n", + dest_hash, + requested ? 1U : 0U, + static_cast(elapsed_ms)); + }, + [](const runtime::PendingPingRequest& request, uint32_t elapsed_ms) + { + char dest_hash[12] = {}; + formatHashPrefix(request.destination_hash, dest_hash, sizeof(dest_hash)); Serial.printf("[LXMF][PingRX] timeout dest=%s elapsed_ms=%lu stage=path\n", dest_hash, static_cast(elapsed_ms)); @@ -3191,60 +3027,7 @@ void LxmfAdapter::pumpPendingPingRequests() sys::ReticulumPingResult::Timeout, elapsed_ms), 100); - pending_ping_requests_.erase( - pending_ping_requests_.begin() + - static_cast(index)); - continue; - } - - if (call_preempt_active) - { - ++index; - continue; - } - - PeerInfo* peer = - findOrLoadPeerByDestinationHash(request.destination_hash); - const bool peer_ready = - peer && - !isZeroBytes(peer->identity_hash, sizeof(peer->identity_hash)) && - !isZeroBytes(peer->sig_pub, sizeof(peer->sig_pub)) && - (peerHasUsableRatchet(*peer) || - !isZeroBytes(peer->enc_pub, sizeof(peer->enc_pub))); - if (peer_ready && - (request.last_send_attempt_ms == 0 || - (now_ms - request.last_send_attempt_ms) >= kPendingPingSendRetryMs)) - { - request.last_send_attempt_ms = now_ms; - Serial.printf("[LXMF][PingTX] dispatch_after_path dest=%s elapsed_ms=%lu\n", - dest_hash, - static_cast(elapsed_ms)); - const MeshActionResult result = - sendReticulumPingToPeer(*peer, request.created_ms); - if (result.ok) - { - pending_ping_requests_.erase( - pending_ping_requests_.begin() + - static_cast(index)); - continue; - } - } - else if (!peer_ready && - (request.last_path_request_ms == 0 || - (now_ms - request.last_path_request_ms) >= - kPathRequestMinIntervalMs)) - { - request.last_path_request_ms = now_ms; - const bool requested = - sendPathRequestForDestination(request.destination_hash); - Serial.printf("[LXMF][PingTX] path_retry dest=%s requested=%u elapsed_ms=%lu\n", - dest_hash, - requested ? 1U : 0U, - static_cast(elapsed_ms)); - } - - ++index; - } + }); } MeshActionResult LxmfAdapter::persistReticulumPeer( @@ -3290,7 +3073,7 @@ MeshActionResult LxmfAdapter::requestNomadPage( destination_text, path ? path : "", isReady() ? 1U : 0U, - static_cast(pending_nomad_page_requests_.size())); + static_cast(network_page_client_.size())); if (!destination_hash || isZeroBytes(destination_hash, reticulum::kTruncatedHashSize) || @@ -3310,46 +3093,49 @@ MeshActionResult LxmfAdapter::requestNomadPage( return MeshActionResult::fail(MeshOperationFailure::NotReady); } - for (const auto& pending : pending_nomad_page_requests_) + PendingNomadPageRequest* queued_request = nullptr; + const runtime::NetworkPageQueueResult queue_result = + network_page_client_.queue(destination_hash, + path, + millis(), + kMaxPendingNomadPageRequests, + kNomadPagePathMaxLen, + &queued_request); + if (queue_result == runtime::NetworkPageQueueResult::Duplicate && + queued_request) { - if (hashesEqual(pending.destination_hash, - destination_hash, - reticulum::kTruncatedHashSize) && - std::strcmp(pending.path, path) == 0) - { - updateNomadPageProgress(pending, - 5, - "Queued Nomad page request", - path, - true, - false, - PageFailureKind::None); - MeshActionResult result = MeshActionResult::success(); - result.detail = 1; - LXMF_NOMAD_PAGE_LOG("queue duplicate dest=%s path=%s\n", - destination_text, - path); - return result; - } + updateNomadPageProgress(*queued_request, + 5, + "Queued Nomad page request", + path, + true, + false, + PageFailureKind::None); + MeshActionResult result = MeshActionResult::success(); + result.detail = 1; + LXMF_NOMAD_PAGE_LOG("queue duplicate dest=%s path=%s\n", + destination_text, + path); + return result; } - - if (pending_nomad_page_requests_.size() >= kMaxPendingNomadPageRequests) + if (queue_result == runtime::NetworkPageQueueResult::Full) { LXMF_NOMAD_PAGE_LOG("queue reject reason=busy dest=%s path=%s depth=%u\n", destination_text, path, - static_cast(pending_nomad_page_requests_.size())); + static_cast(network_page_client_.size())); return MeshActionResult::fail(MeshOperationFailure::Busy); } + if (queue_result != runtime::NetworkPageQueueResult::Queued || + !queued_request) + { + LXMF_NOMAD_PAGE_LOG("queue reject reason=invalid_owner_result dest=%s path=%s\n", + destination_text, + path); + return MeshActionResult::fail(MeshOperationFailure::InvalidInput); + } - PendingNomadPageRequest request{}; - copyHash(request.destination_hash, - destination_hash, - sizeof(request.destination_hash)); - std::snprintf(request.path, sizeof(request.path), "%s", path); - request.created_ms = millis(); - pending_nomad_page_requests_.push_back(request); - updateNomadPageProgress(pending_nomad_page_requests_.back(), + updateNomadPageProgress(*queued_request, 5, "Queued Nomad page request", path, @@ -3360,7 +3146,7 @@ MeshActionResult LxmfAdapter::requestNomadPage( LXMF_NOMAD_PAGE_LOG("queued dest=%s path=%s pending=%u\n", destination_text, path, - static_cast(pending_nomad_page_requests_.size())); + static_cast(network_page_client_.size())); pumpNomadPageRequests(); return MeshActionResult::success(); } @@ -3403,7 +3189,7 @@ void LxmfAdapter::applyConfig(const MeshConfig& config) delivery_hex, propagation_hex, config_.reticulum_anonymous_peer ? 1 : 0, - static_cast(peers_.size())); + static_cast(destination_registry_.size())); } last_announce_ms_ = millis(); last_announce_attempt_ms_ = 0; @@ -3468,7 +3254,7 @@ LxmfAdapter::RuntimeBudget LxmfAdapter::makeRuntimeBudget() const return budget; } - if (!pending_nomad_page_requests_.empty()) + if (!network_page_client_.empty()) { budget.live_packet_limit = kMaxIngressPacketsPerPoll; budget.deferred_discovery_limit = 0; @@ -3526,41 +3312,20 @@ void LxmfAdapter::processRuntime() const auto network_status = rtnet::status(); if (network_status.generation != network_config_generation_) { - for (auto& session : links_.sessions) - { - if (session.state != LinkState::Closed) + link_manager_.forEachSession( + [this](LinkSession& session) { - closeLinkSession(session, LinkCloseReason::Error); - } - } - links_.sessions.clear(); - - transport_.paths.clear(); - transport_.reverse_table.clear(); - transport_.pending_path_requests.clear(); - transport_.link_relays.clear(); + if (session.state != LinkState::Closed) + { + closeLinkSession(session, LinkCloseReason::Error); + } + }); + link_manager_.clear(); + path_manager_.clear(); deferred_discovery_queue_.clear(); - propagation_stamp_.reset(); - for (auto& upload : propagation_.pending_uploads) - { - if (upload.state == runtime::PropagationUploadState::Stamping) - { - upload.state = runtime::PropagationUploadState::NeedsStamp; - } - } - propagation_.sync_wants.clear(); - propagation_.sync_haves.clear(); - propagation_.sync_stage = PropagationSyncStage::Idle; - propagation_.has_active_node = false; - propagation_.initial_sync_pending = - rtnet::active().propagation.sync_on_start; - std::memset(propagation_.active_node_hash, - 0, - sizeof(propagation_.active_node_hash)); - std::memset(propagation_.sync_request_id, - 0, - sizeof(propagation_.sync_request_id)); + propagation_client_.resetForNetworkConfig( + rtnet::active().propagation.sync_on_start); interfaces_.applyConfig(config_, rtnet::active()); network_config_generation_ = network_status.generation; @@ -3587,9 +3352,7 @@ void LxmfAdapter::processRuntime() kPropagationEntryTtlS, kPropagationTransientTtlS, kPropagationEntryTtlS}; - runtime::cullPropagationRuntime(propagation_, - currentTimestampSeconds(), - propagation_limits); + propagation_client_.cull(currentTimestampSeconds(), propagation_limits); pumpNomadPageRequests(); processPropagationClient(); @@ -3800,24 +3563,19 @@ bool LxmfAdapter::processOneRadioPacket( rememberPacket(packet_hash); - if (parsed.packet_type == reticulum::PacketType::Announce) + switch (packet_router_.route(parsed)) { + case runtime::PacketRoute::Announce: return handleAnnouncePacket(packet, packet_len, parsed, ingress_interface, budget.allow_persistence || deferred_replay); - } - if (parsed.packet_type == reticulum::PacketType::Proof) - { + case runtime::PacketRoute::Proof: return handleProofPacket(packet, packet_len, parsed, ingress_interface); - } - if (parsed.packet_type == reticulum::PacketType::LinkRequest) - { + case runtime::PacketRoute::LinkRequest: return handleLinkRequestPacket(packet, packet_len, parsed, ingress_interface); - } - if (parsed.packet_type == reticulum::PacketType::Data) - { + case runtime::PacketRoute::Data: if (!handlePathRequestPacket(parsed) && !handleCacheRequestPacket(parsed) && !handleLocalLinkPacket(packet, packet_len, parsed, ingress_interface) && @@ -3827,6 +3585,8 @@ bool LxmfAdapter::processOneRadioPacket( return handleDataPacket(packet, packet_len, parsed); } return true; + case runtime::PacketRoute::LinkOrTransport: + break; } const bool handled_local = @@ -4190,203 +3950,83 @@ bool LxmfAdapter::handleAnnouncePacket(const uint8_t* raw_packet, size_t raw_len reticulum::interfaces::InterfaceKind ingress_interface, bool allow_persistence) { - if (!raw_packet || raw_len == 0 || !packet.destination_hash) - { - return false; - } - if (packet.destination_type != reticulum::DestinationType::Single) - { - return false; - } - if (packet.context != static_cast(reticulum::PacketContext::None) && - packet.context != static_cast(reticulum::PacketContext::PathResponse)) - { - return false; - } - - reticulum::ParsedAnnounce announce{}; - if (!reticulum::parseAnnounce(packet, &announce) || !announce.valid) - { - char packet_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; - formatHashHex(packet.destination_hash, - reticulum::kTruncatedHashSize, - packet_hash_hex, - sizeof(packet_hash_hex)); - Serial.printf("[LXMF][AnnounceRX] drop reason=parse_failed dest=%s payload_len=%u\n", - packet_hash_hex, - static_cast(packet.payload_len)); - return false; - } - - uint8_t identity_hash[reticulum::kTruncatedHashSize] = {}; - reticulum::computeIdentityHash(announce.public_key, identity_hash); - - uint8_t expected_destination_hash[reticulum::kTruncatedHashSize] = {}; - reticulum::computeDestinationHash(announce.name_hash, identity_hash, expected_destination_hash); - if (!hashesEqual(expected_destination_hash, packet.destination_hash, reticulum::kTruncatedHashSize)) - { - char packet_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; - char expected_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; - formatHashHex(packet.destination_hash, - reticulum::kTruncatedHashSize, - packet_hash_hex, - sizeof(packet_hash_hex)); - formatHashHex(expected_destination_hash, - sizeof(expected_destination_hash), - expected_hash_hex, - sizeof(expected_hash_hex)); - Serial.printf("[LXMF][AnnounceRX] drop reason=destination_mismatch packet=%s expected=%s\n", - packet_hash_hex, - expected_hash_hex); - return false; - } - - uint8_t* signed_data = announce_rx_signed_scratch_; - size_t signed_len = 0; - memcpy(signed_data + signed_len, packet.destination_hash, reticulum::kTruncatedHashSize); - signed_len += reticulum::kTruncatedHashSize; - memcpy(signed_data + signed_len, announce.public_key, reticulum::kCombinedPublicKeySize); - signed_len += reticulum::kCombinedPublicKeySize; - memcpy(signed_data + signed_len, announce.name_hash, reticulum::kNameHashSize); - signed_len += reticulum::kNameHashSize; - memcpy(signed_data + signed_len, announce.random_hash, 10); - signed_len += 10; - if (announce.has_ratchet && announce.ratchet && announce.ratchet_len != 0) - { - if (signed_len + announce.ratchet_len > reticulum::kReticulumMtu) - { - return false; - } - memcpy(signed_data + signed_len, announce.ratchet, announce.ratchet_len); - signed_len += announce.ratchet_len; - } - if (announce.app_data_len != 0) - { - if (signed_len + announce.app_data_len > reticulum::kReticulumMtu) - { - return false; - } - memcpy(signed_data + signed_len, announce.app_data, announce.app_data_len); - signed_len += announce.app_data_len; - } - - const uint8_t* sig_pub = announce.public_key + reticulum::kEncryptionPublicKeySize; - if (!LxmfIdentity::verify(sig_pub, announce.signature, signed_data, signed_len)) - { - char packet_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; - formatHashHex(packet.destination_hash, - reticulum::kTruncatedHashSize, - packet_hash_hex, - sizeof(packet_hash_hex)); - Serial.printf("[LXMF][AnnounceRX] drop reason=signature_failed dest=%s\n", - packet_hash_hex); - return false; - } - - LocalDestinationKind local_kind = LocalDestinationKind::Delivery; - const bool local_destination = - isLocalDestinationHash(packet.destination_hash, &local_kind); - if (local_destination) - { - char destination_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; - formatHashHex(packet.destination_hash, - reticulum::kTruncatedHashSize, - destination_hex, - sizeof(destination_hex)); - Serial.printf("[LXMF][AnnounceRX] ignore reason=local_destination dest=%s kind=%s\n", - destination_hex, - localDestinationKindLabel(local_kind)); - return true; - } - if (packet.hops > kMaxTransportHops) - { - Serial.printf("[LXMF][AnnounceRX] ignore reason=max_hops hops=%u\n", - static_cast(packet.hops)); - return true; - } - const uint32_t now_ms = millis(); - const PathEntry* existing_path = runtime::findPath(transport_, packet.destination_hash); - const runtime::PathAnnounceDecision path_decision = - runtime::evaluatePathAnnounce(existing_path, - packet.hops, - announce.random_hash, - now_ms, - kPathTtlMs); - if (!runtime::pathAnnounceAccepted(path_decision)) + const uint32_t now_s = currentTimestampSeconds(); + runtime::AnnounceIngestOptions options{}; + options.now_ms = now_ms; + options.now_s = now_s; + options.path_ttl_ms = kPathTtlMs; + options.directory_address_refresh_interval_s = + kDirectoryAddressRefreshIntervalS; + options.max_paths = kMaxPaths; + options.max_transport_hops = kMaxTransportHops; + options.ingress_interface_id = active_ingress_interface_id_; + options.ingress_interface = ingress_interface; + options.local_destination_context = this; + options.resolve_local_destination = &LxmfAdapter::resolveLocalDestinationForAnnounce; + + runtime::AnnounceIngestResult ingest{}; + if (!announce_ingestor_.ingest(raw_packet, + raw_len, + packet, + identity_, + destination_registry_, + path_manager_, + options, + &ingest)) { - char destination_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; - formatHashHex(packet.destination_hash, - reticulum::kTruncatedHashSize, - destination_hex, - sizeof(destination_hex)); - Serial.printf("[LXMF][AnnounceRX] ignore reason=path_%s dest=%s hops=%u previous_hops=%u\n", - pathAnnounceDecisionLabel(path_decision), - destination_hex, - static_cast(packet.hops), - static_cast(existing_path ? existing_path->hops : 0)); + return false; + } + + if (ingest.status == runtime::AnnounceIngestResult::Status::Ignored) + { + if (ingest.local_destination) + { + char destination_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; + formatHashHex(packet.destination_hash, + reticulum::kTruncatedHashSize, + destination_hex, + sizeof(destination_hex)); + Serial.printf("[LXMF][AnnounceRX] ignore reason=local_destination dest=%s kind=%s\n", + destination_hex, + localDestinationKindLabel(ingest.local_kind)); + } return true; } - - PathEntry& path = upsertPath(packet.destination_hash); - const uint32_t now_s = currentTimestampSeconds(); - runtime::applyPathAnnounce(path, - packet.hops, - announce.random_hash, - now_ms, - now_s); - path.interface_id = active_ingress_interface_id_; - path.direct = (packet.transport_id == nullptr); - resolvePendingPathRequest(packet.destination_hash); - if (packet.transport_id) + if (ingest.status != runtime::AnnounceIngestResult::Status::Accepted || + !ingest.path) { - copyHash(path.next_hop_transport, packet.transport_id, sizeof(path.next_hop_transport)); - } - else - { - copyHash(path.next_hop_transport, packet.destination_hash, sizeof(path.next_hop_transport)); + return false; } - for (auto& session : links_.sessions) - { - if (!session.initiator || - session.state != LinkState::Pending || - !hashesEqual(session.remote_destination_hash, - packet.destination_hash, - sizeof(session.remote_destination_hash))) + PathEntry& path = *ingest.path; + link_manager_.forEachSession( + [this, &packet, &path](LinkSession& session) { - continue; - } + if (!session.initiator || + session.state != LinkState::Pending || + !hashesEqual(session.remote_destination_hash, + packet.destination_hash, + sizeof(session.remote_destination_hash))) + { + return; + } - session.expected_hops = path.hops; - const bool retried = sendLinkRequest(session); - char dest_hash[12] = {}; - formatHashPrefix(packet.destination_hash, dest_hash, sizeof(dest_hash)); - Serial.printf("[LXMF][LinkTX] retry_after_path dest=%s kind=%u ok=%u\n", - dest_hash, - static_cast(session.destination), - retried ? 1U : 0U); - } - - if (raw_len <= sizeof(path.cached_announce)) - { - memcpy(path.cached_announce, raw_packet, raw_len); - path.cached_announce_len = raw_len; - reticulum::computePacketHash(raw_packet, raw_len, path.cached_packet_hash); - } + session.expected_hops = path.hops; + const bool retried = sendLinkRequest(session); + char dest_hash[12] = {}; + formatHashPrefix(packet.destination_hash, dest_hash, sizeof(dest_hash)); + Serial.printf("[LXMF][LinkTX] retry_after_path dest=%s kind=%u ok=%u\n", + dest_hash, + static_cast(session.destination), + retried ? 1U : 0U); + }); if (shouldRebroadcastAnnounce(packet, ingress_interface)) { (void)rebroadcastAnnounce(path, packet); } - const bool delivery_announce = isLxmfDeliveryAnnounce(announce); - const bool propagation_announce = isLxmfPropagationAnnounce(announce); - const bool call_audio_announce = isCallAudioAnnounce(announce); - const bool lxst_telephony_announce = - isLxstTelephonyAnnounce(announce); - const bool nomad_node_announce = isNomadNetworkNodeAnnounce(announce); - char packet_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; char identity_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; char announce_ratchet_id[12] = {}; @@ -4394,88 +4034,31 @@ bool LxmfAdapter::handleAnnouncePacket(const uint8_t* raw_packet, size_t raw_len reticulum::kTruncatedHashSize, packet_hash_hex, sizeof(packet_hash_hex)); - formatHashHex(identity_hash, - sizeof(identity_hash), + formatHashHex(ingest.identity_hash, + sizeof(ingest.identity_hash), identity_hash_hex, sizeof(identity_hash_hex)); - const bool packet_has_ratchet = - announce.has_ratchet && - announce.ratchet && - announce.ratchet_len == reticulum::kRatchetSize && - !isZeroBytes(announce.ratchet, announce.ratchet_len); - formatRatchetIdPrefix(packet_has_ratchet ? announce.ratchet : nullptr, + formatRatchetIdPrefix(ingest.packet_has_ratchet ? ingest.announce.ratchet : nullptr, announce_ratchet_id, sizeof(announce_ratchet_id)); - char announce_display_name[32] = {}; - bool has_stamp_cost = false; - uint8_t stamp_cost = 0; - if (call_audio_announce && announce.app_data && announce.app_data_len != 0) - { - (void)copyTextAppDataDisplayName(announce.app_data, - announce.app_data_len, - announce_display_name, - sizeof(announce_display_name)); - } - else if (delivery_announce && announce.app_data && announce.app_data_len != 0 && - unpackPeerAnnounceAppData(announce.app_data, - announce.app_data_len, - announce_display_name, - sizeof(announce_display_name), - &has_stamp_cost, - &stamp_cost)) - { - (void)has_stamp_cost; - (void)stamp_cost; - } - else if (nomad_node_announce && announce.app_data && announce.app_data_len != 0) - { - (void)copyTextAppDataDisplayName(announce.app_data, - announce.app_data_len, - announce_display_name, - sizeof(announce_display_name)); - } - else if (!(delivery_announce || propagation_announce || call_audio_announce || - nomad_node_announce) && - announce.app_data && announce.app_data_len != 0) - { - (void)copyTextAppDataDisplayName(announce.app_data, - announce.app_data_len, - announce_display_name, - sizeof(announce_display_name)); - } - if ((delivery_announce || - (call_audio_announce && !lxst_telephony_announce)) && - announce_display_name[0] == '\0') - { - copyCString(announce_display_name, - sizeof(announce_display_name), - kAnonymousPeerDisplayName); - } - else if (nomad_node_announce && announce_display_name[0] == '\0') - { - copyCString(announce_display_name, - sizeof(announce_display_name), - kAnonymousNodeDisplayName); - } - rtdir::AnnounceRecord directory_announce{}; directory_announce.valid = true; copyHash(directory_announce.destination_hash, packet.destination_hash, sizeof(directory_announce.destination_hash)); copyHash(directory_announce.identity_hash, - identity_hash, + ingest.identity_hash, sizeof(directory_announce.identity_hash)); directory_announce.aspect = - delivery_announce + ingest.delivery_announce ? rtdir::AnnounceAspect::LxmfDelivery - : (propagation_announce + : (ingest.propagation_announce ? rtdir::AnnounceAspect::LxmfPropagation - : (call_audio_announce ? rtdir::AnnounceAspect::CallAudio - : (nomad_node_announce - ? rtdir::AnnounceAspect::NomadNetworkNode - : rtdir::AnnounceAspect::Unknown))); + : (ingest.call_audio_announce ? rtdir::AnnounceAspect::CallAudio + : (ingest.nomad_node_announce + ? rtdir::AnnounceAspect::NomadNetworkNode + : rtdir::AnnounceAspect::Unknown))); directory_announce.source = packet.context == static_cast(reticulum::PacketContext::PathResponse) ? rtdir::EntrySource::PathResponse @@ -4485,16 +4068,16 @@ bool LxmfAdapter::handleAnnouncePacket(const uint8_t* raw_packet, size_t raw_len directory_announce.hops = packet.hops; directory_announce.path_response = packet.context == static_cast(reticulum::PacketContext::PathResponse); - directory_announce.local_destination = local_destination; - directory_announce.delivery = delivery_announce; - directory_announce.propagation = propagation_announce; + directory_announce.local_destination = ingest.local_destination; + directory_announce.delivery = ingest.delivery_announce; + directory_announce.propagation = ingest.propagation_announce; copyCString(directory_announce.display_name, sizeof(directory_announce.display_name), - announce_display_name); + ingest.display_name); directory_announce.raw_packet = raw_packet; directory_announce.raw_packet_len = raw_len; - directory_announce.app_data = announce.app_data; - directory_announce.app_data_len = announce.app_data_len; + directory_announce.app_data = ingest.announce.app_data; + directory_announce.app_data_len = ingest.announce.app_data_len; if (allow_persistence) { const auto announce_store_status = rtdir::record_announce(directory_announce); @@ -4508,12 +4091,11 @@ bool LxmfAdapter::handleAnnouncePacket(const uint8_t* raw_packet, size_t raw_len bool log_announce_detail = ingress_interface != reticulum::interfaces::InterfaceKind::WifiGateway || - local_destination; - const bool contact_announce = delivery_announce || lxst_telephony_announce; + ingest.local_destination; if (log_announce_detail && ingress_interface != reticulum::interfaces::InterfaceKind::WifiGateway && - !local_destination && - !contact_announce) + !ingest.local_destination && + !ingest.contact_announce) { const uint32_t log_now_ms = millis(); if (last_lora_announce_ignore_log_ms_ != 0 && @@ -4542,153 +4124,73 @@ bool LxmfAdapter::handleAnnouncePacket(const uint8_t* raw_packet, size_t raw_len static_cast(packet.hops), static_cast(packet.context), static_cast(packet.context_flag), - static_cast(announce.app_data_len), - packet_has_ratchet ? 1U : 0U, + static_cast(ingest.announce.app_data_len), + ingest.packet_has_ratchet ? 1U : 0U, announce_ratchet_id, - delivery_announce ? 1U : 0U, - propagation_announce ? 1U : 0U, - call_audio_announce ? 1U : 0U, - nomad_node_announce ? 1U : 0U, - local_destination ? 1U : 0U, - localDestinationKindLabel(local_kind)); + ingest.delivery_announce ? 1U : 0U, + ingest.propagation_announce ? 1U : 0U, + ingest.call_audio_announce ? 1U : 0U, + ingest.nomad_node_announce ? 1U : 0U, + ingest.local_destination ? 1U : 0U, + localDestinationKindLabel(ingest.local_kind)); } - if (propagation_announce && !local_destination) + if (ingest.propagation_announce && !ingest.local_destination) { uint8_t delivery_hash[reticulum::kTruncatedHashSize] = {}; - destinationHashForAspect(identity_hash, "delivery", delivery_hash); - PropagationPeerState& propagation_peer = - runtime::upsertPropagationPeer(propagation_, - packet.destination_hash, - delivery_hash, - identity_hash, - kMaxPropagationPeers); + destinationHashForAspect(ingest.identity_hash, "delivery", delivery_hash); DecodedPropagationAnnounce propagation_announce_data{}; const bool propagation_data_valid = - decodePropagationAnnounceAppData(announce.app_data, - announce.app_data_len, + decodePropagationAnnounceAppData(ingest.announce.app_data, + ingest.announce.app_data_len, &propagation_announce_data) && propagation_announce_data.valid; - propagation_peer.node_active = propagation_data_valid; - propagation_peer.hops = packet.hops; - if (propagation_data_valid) + if (!propagation_data_valid) { - propagation_peer.announce_timebase_s = - propagation_announce_data.timebase_s; - propagation_peer.transfer_limit_kb = - propagation_announce_data.transfer_limit_kb; - propagation_peer.sync_limit_kb = - propagation_announce_data.sync_limit_kb; - propagation_peer.stamp_cost = - propagation_announce_data.stamp_cost; - propagation_peer.stamp_cost_flexibility = - propagation_announce_data.stamp_cost_flexibility; - propagation_peer.peering_cost = - propagation_announce_data.peering_cost; - if (announce.public_key) - { - memcpy(propagation_peer.enc_pub, - announce.public_key, - sizeof(propagation_peer.enc_pub)); - memcpy(propagation_peer.sig_pub, - announce.public_key + sizeof(propagation_peer.enc_pub), - sizeof(propagation_peer.sig_pub)); - } - std::snprintf(propagation_peer.display_name, - sizeof(propagation_peer.display_name), - "%s", - propagation_announce_data.display_name.c_str()); + propagation_announce_data.valid = false; + } + const PropagationPeerState* propagation_peer = + propagation_client_.notePeerAnnounce(packet.destination_hash, + delivery_hash, + ingest.identity_hash, + packet.hops, + propagation_announce_data, + ingest.announce.public_key, + now_s, + kMaxPropagationPeers); + if (!propagation_peer) + { + return true; } - runtime::markPropagationPeerSeen(propagation_peer, now_s); Serial.printf("[LXMF][Propagation] node_seen dest=%s active=%u hops=%u cost=%u transfer_kb=%lu sync_kb=%lu name=\"%s\"\n", packet_hash_hex, - propagation_peer.node_active ? 1U : 0U, - static_cast(propagation_peer.hops), - static_cast(propagation_peer.stamp_cost), - static_cast(propagation_peer.transfer_limit_kb), - static_cast(propagation_peer.sync_limit_kb), - propagation_peer.display_name); + propagation_peer->node_active ? 1U : 0U, + static_cast(propagation_peer->hops), + static_cast(propagation_peer->stamp_cost), + static_cast( + propagation_peer->transfer_limit_kb), + static_cast( + propagation_peer->sync_limit_kb), + propagation_peer->display_name); } - if (!contact_announce || local_destination) + if (!ingest.contact_announce || ingest.local_destination) { if (log_announce_detail) { Serial.printf("[LXMF][AnnounceRX] ignore reason=%s dest=%s\n", - local_destination ? "local_destination" : "not_contact_announce", + ingest.local_destination ? "local_destination" : "not_contact_announce", packet_hash_hex); } return true; } - uint8_t peer_destination_hash[reticulum::kTruncatedHashSize] = {}; - if (delivery_announce) + if (!ingest.learned_peer) { - copyHash(peer_destination_hash, packet.destination_hash, sizeof(peer_destination_hash)); + return true; } - else - { - destinationHashForAspect(identity_hash, "delivery", peer_destination_hash); - } - - PeerInfo& peer = upsertPeer(peer_destination_hash); - const uint32_t previous_seen_s = peer.last_seen_s; - const bool delivery_ratchet_available = delivery_announce && packet_has_ratchet; - const bool ratchet_changed = - delivery_announce && - (peerHasUsableRatchet(peer) != delivery_ratchet_available || - (delivery_ratchet_available && - std::memcmp(peer.ratchet_pub, - announce.ratchet, - sizeof(peer.ratchet_pub)) != 0)); - const bool identity_changed = - isZeroBytes(peer.identity_hash, sizeof(peer.identity_hash)) || - !hashesEqual(peer.identity_hash, identity_hash, sizeof(peer.identity_hash)) || - memcmp(peer.enc_pub, announce.public_key, sizeof(peer.enc_pub)) != 0 || - memcmp(peer.sig_pub, sig_pub, sizeof(peer.sig_pub)) != 0; - const bool display_changed = - announce_display_name[0] != '\0' && - strncmp(peer.display_name, announce_display_name, sizeof(peer.display_name)) != 0; - copyHash(peer.identity_hash, identity_hash, sizeof(peer.identity_hash)); - memcpy(peer.enc_pub, announce.public_key, sizeof(peer.enc_pub)); - memcpy(peer.sig_pub, sig_pub, sizeof(peer.sig_pub)); - peer.last_seen_s = now_s; - if (delivery_announce) - { - if (delivery_ratchet_available) - { - memcpy(peer.ratchet_pub, announce.ratchet, sizeof(peer.ratchet_pub)); - peer.has_ratchet = true; - peer.ratchet_seen_s = now_s; - } - else - { - memset(peer.ratchet_pub, 0, sizeof(peer.ratchet_pub)); - peer.has_ratchet = false; - peer.ratchet_seen_s = 0; - } - } - - if (announce_display_name[0] != '\0') - { - copyCString(peer.display_name, sizeof(peer.display_name), announce_display_name); - } - else if (peer.display_name[0] == '\0') - { - copyCString(peer.display_name, sizeof(peer.display_name), kAnonymousPeerDisplayName); - } - - const bool address_refresh_due = - previous_seen_s == 0 || - (now_s >= previous_seen_s && - (now_s - previous_seen_s) >= kDirectoryAddressRefreshIntervalS); - const bool should_store_address = - ingress_interface != reticulum::interfaces::InterfaceKind::WifiGateway || - identity_changed || - ratchet_changed || - display_changed || - address_refresh_due; - if (allow_persistence && should_store_address) + PeerInfo& peer = *ingest.learned_peer; + if (allow_persistence && ingest.should_store_address) { if (!recordPeerInDirectory( peer, @@ -4726,7 +4228,7 @@ bool LxmfAdapter::handleAnnouncePacket(const uint8_t* raw_packet, size_t raw_len peer.display_name[0] != '\0' ? peer.display_name : "", peerHasUsableRatchet(peer) ? 1U : 0U, peer_ratchet_id, - static_cast(peers_.size())); + static_cast(destination_registry_.size())); return true; } @@ -4890,7 +4392,7 @@ bool LxmfAdapter::handleProofPacket( { return true; } - if (!pending_nomad_page_requests_.empty() && + if (!network_page_client_.empty() && packet.context == static_cast(reticulum::PacketContext::LrProof)) { char link_hash[12] = {}; @@ -4903,7 +4405,7 @@ bool LxmfAdapter::handleProofPacket( static_cast(raw_len), static_cast(packet.payload_len), static_cast(packet.hops), - static_cast(pending_nomad_page_requests_.size())); + static_cast(network_page_client_.size())); } } @@ -4934,8 +4436,7 @@ bool LxmfAdapter::handleProofPacket( }; if (runtime::PendingDeliveryReceipt* pending = - runtime::findPendingDeliveryReceipt(transport_, - packet.destination_hash)) + path_manager_.findPendingDeliveryReceipt(packet.destination_hash)) { const uint8_t* signature = proof_signature_for_hash(pending->packet_hash); @@ -4960,8 +4461,7 @@ bool LxmfAdapter::handleProofPacket( const MessageId message_id = pending->message_id; const uint32_t elapsed_ms = millis() - pending->created_ms; - runtime::removePendingDeliveryReceipt(transport_, - packet.destination_hash); + path_manager_.removePendingDeliveryReceipt(packet.destination_hash); Serial.printf("[LXMF][DirectTX] proof_ok msg=%lu representation=opportunistic elapsed_ms=%lu hops=%u\n", static_cast(message_id), static_cast(elapsed_ms), @@ -4974,7 +4474,7 @@ bool LxmfAdapter::handleProofPacket( } if (runtime::PendingPingReceipt* pending = - runtime::findPendingPingReceipt(transport_, packet.destination_hash)) + path_manager_.findPendingPingReceipt(packet.destination_hash)) { const uint8_t* signature = proof_signature_for_hash(pending->packet_hash); @@ -5010,7 +4510,7 @@ bool LxmfAdapter::handleProofPacket( rtt_ms, packet.hops), 100); - runtime::removePendingPingReceipt(transport_, packet.destination_hash); + path_manager_.removePendingPingReceipt(packet.destination_hash); return true; } @@ -5119,30 +4619,13 @@ bool LxmfAdapter::handleLinkRequestPacket( LinkSession* session = findLinkSession(link_id); if (!session) { - if (links_.sessions.size() >= kMaxLinkSessions) + session = link_manager_.appendSessionPreserving( + kMaxLinkSessions, + reject_busy_call ? current_call_link_id : nullptr); + if (!session) { - auto discard = links_.sessions.begin(); - if (reject_busy_call) - { - discard = std::find_if( - links_.sessions.begin(), - links_.sessions.end(), - [¤t_call_link_id](const LinkSession& candidate) - { - return !hashesEqual(candidate.link_id, - current_call_link_id, - sizeof(candidate.link_id)); - }); - if (discard == links_.sessions.end()) - { - return false; - } - } - links_.sessions.erase(discard); + return false; } - - links_.sessions.push_back(LinkSession{}); - session = &links_.sessions.back(); copyHash(session->link_id, link_id, sizeof(session->link_id)); memcpy(session->peer_enc_pub, packet.payload, LxmfIdentity::kEncPubKeySize); memcpy(session->peer_link_sig_pub, @@ -5172,7 +4655,7 @@ bool LxmfAdapter::handleLinkRequestPacket( if (!deriveLinkKey(*session)) { - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return false; } @@ -5205,7 +4688,7 @@ bool LxmfAdapter::handleLinkRequestPacket( busy_sent ? 1U : 0U, close_sent ? 1U : 0U, static_cast(ingress_interface)); - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return proof_sent && busy_sent && close_sent; } @@ -5460,14 +4943,21 @@ bool LxmfAdapter::handleLinkDataPacket(LinkSession& session, } else if (session.destination == LocalDestinationKind::CallAudio) { - handled = - session.call_wire_profile == - ReticulumCallWireProfile::SidebandLxst - ? handleLxstPacket(session, payload_ptr, payload_len) - : ::platform::ui::reticulum_call::enqueue_inbound_audio( - session.link_id, - payload_ptr, - payload_len); + if (session.call_wire_profile == + ReticulumCallWireProfile::SidebandLxst) + { + handled = handleLxstPacket(session, payload_ptr, payload_len); + } +#if TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT + else if (session.call_wire_profile == + ReticulumCallWireProfile::MeshChatCallAudio) + { + handled = ::platform::ui::reticulum_call::enqueue_inbound_audio( + session.link_id, + payload_ptr, + payload_len); + } +#endif } else if (session.destination == LocalDestinationKind::Delivery) { @@ -5561,12 +5051,17 @@ bool LxmfAdapter::handleLinkDataPacket(LinkSession& session, { for (auto& pending : session.pending_requests) { - if (pending.request_id == response.request_id) + if (pending.request_id.size() == response.request_id.size() && + (pending.request_id.empty() || + std::memcmp(pending.request_id.data(), + response.request_id.data(), + pending.request_id.size()) == 0)) { pending.response_ready = true; if (!response.data_is_nil) { - pending.response = std::move(response.packed_data); + pending.response.assign(response.packed_data.begin(), + response.packed_data.end()); } handled = true; break; @@ -5652,7 +5147,7 @@ bool LxmfAdapter::handleLinkDataPacket(LinkSession& session, { if (payload_len == reticulum::kFullHashSize) { - (void)runtime::eraseLinkResourceByHash(session.incoming_resources, payload_ptr); + (void)link_manager_.eraseIncomingResource(session, payload_ptr); handled = true; } should_prove = handled; @@ -5661,7 +5156,7 @@ bool LxmfAdapter::handleLinkDataPacket(LinkSession& session, { if (payload_len == reticulum::kFullHashSize) { - (void)runtime::eraseLinkResourceByHash(session.outgoing_resources, payload_ptr); + (void)link_manager_.eraseOutgoingResource(session, payload_ptr); handled = true; } should_prove = handled; @@ -5954,12 +5449,14 @@ bool LxmfAdapter::handleLinkProofPacket(LinkSession& session, updateCallRuntimePeer(session, peer); ::platform::ui::reticulum_call::mark_link_active( session.link_id); +#if TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT if (session.call_wire_profile == ReticulumCallWireProfile::MeshChatCallAudio) { (void)sendLinkIdentify(session); } else +#endif { (void)dispatchLxstCallEvent( session, @@ -6025,7 +5522,7 @@ bool LxmfAdapter::handleLinkProofPacket(LinkSession& session, const MessageStatus status = session.destination == LocalDestinationKind::Delivery ? MessageStatus::Delivered - : MessageStatus::Sent; + : MessageStatus::Queued; sys::EventBus::publish(new sys::ChatSendResultEvent(message_id, status), 0); } @@ -6093,7 +5590,7 @@ bool LxmfAdapter::handleLinkResourceAdvertisement(LinkSession& session, return false; } - if (runtime::findLinkResource(session.incoming_resources, advertisement.resource_hash)) + if (link_manager_.findIncomingResource(session, advertisement.resource_hash)) { return true; } @@ -6115,39 +5612,40 @@ bool LxmfAdapter::handleLinkResourceAdvertisement(LinkSession& session, static_cast(advertisement.request_id.size())); } - LinkResourceTransfer resource{}; - if (!runtime::initialiseIncomingResourceTransfer(resource, - advertisement.resource_hash, - advertisement.random_hash, - advertisement.original_hash, - std::move(advertisement.request_id), - std::move(advertisement.hashmap), - advertisement.data_size, - advertisement.transfer_size, - advertisement.part_count, - advertisement.segment_index, - advertisement.total_segments, - advertisement.flags, - encrypted, - compressed, - has_metadata, - split, - millis(), - kResourceWindowSize)) + LinkResourceTransfer* incoming_resource = + link_manager_.startIncomingResource(session, + advertisement.resource_hash, + advertisement.random_hash, + advertisement.original_hash, + advertisement.request_id.data(), + advertisement.request_id.size(), + advertisement.hashmap.data(), + advertisement.hashmap.size(), + advertisement.data_size, + advertisement.transfer_size, + advertisement.part_count, + advertisement.segment_index, + advertisement.total_segments, + advertisement.flags, + encrypted, + compressed, + has_metadata, + split, + millis(), + kResourceWindowSize); + if (!incoming_resource) { return false; } - session.incoming_resources.push_back(std::move(resource)); - LinkResourceTransfer& incoming_resource = session.incoming_resources.back(); if (session.destination == LocalDestinationKind::NomadPage && - !incoming_resource.request_id.empty()) + !incoming_resource->request_id.empty()) { if (PendingNomadPageRequest* page_request = findPendingNomadPageRequestById( session.remote_destination_hash, - incoming_resource.request_id.data(), - incoming_resource.request_id.size())) + incoming_resource->request_id.data(), + incoming_resource->request_id.size())) { char detail[32] = {}; std::snprintf(detail, @@ -6163,7 +5661,7 @@ bool LxmfAdapter::handleLinkResourceAdvertisement(LinkSession& session, PageFailureKind::None); } } - return requestNextResourceWindow(session, incoming_resource); + return requestNextResourceWindow(session, *incoming_resource); } bool LxmfAdapter::requestNextResourceWindow(LinkSession& session, @@ -6175,13 +5673,13 @@ bool LxmfAdapter::requestNextResourceWindow(LinkSession& session, } const runtime::ResourceWindowRequest request = - runtime::buildNextResourceWindowRequest(resource); + link_manager_.buildNextResourceWindowRequest(resource); if (!request.valid) { return false; } - std::vector request_data; + runtime::ResourceMetadataBuffer request_data; request_data.reserve(1 + kResourceMapHashLen + reticulum::kFullHashSize + (request.requested_hashes.size() * kResourceMapHashLen)); if (request.needs_more_hashmap) @@ -6203,7 +5701,9 @@ bool LxmfAdapter::requestNextResourceWindow(LinkSession& session, request_data.insert(request_data.end(), requested_hash.begin(), requested_hash.end()); } - runtime::noteResourceWindowRequest(resource, request.needs_more_hashmap, millis()); + link_manager_.noteResourceWindowRequested(resource, + request.needs_more_hashmap, + millis()); const bool ok = sendLinkPacket(session, reticulum::PacketType::Data, reticulum::PacketContext::ResourceReq, @@ -6263,7 +5763,8 @@ bool LxmfAdapter::handleLinkResourceRequest(LinkSession& session, const uint8_t* resource_hash = plaintext + offset; offset += reticulum::kFullHashSize; - LinkResourceTransfer* resource = runtime::findLinkResource(session.outgoing_resources, resource_hash); + LinkResourceTransfer* resource = + link_manager_.findOutgoingResource(session, resource_hash); if (!resource) { return false; @@ -6339,7 +5840,7 @@ bool LxmfAdapter::handleLinkResourceRequest(LinkSession& session, } } - resource->last_activity_ms = millis(); + link_manager_.touchResource(*resource, millis()); return sent_any; } @@ -6351,7 +5852,8 @@ bool LxmfAdapter::handleLinkResourceHashmapUpdate(LinkSession& session, return false; } - LinkResourceTransfer* resource = runtime::findLinkResource(session.incoming_resources, plaintext); + LinkResourceTransfer* resource = + link_manager_.findIncomingResource(session, plaintext); if (!resource) { return false; @@ -6373,11 +5875,12 @@ bool LxmfAdapter::handleLinkResourceHashmapUpdate(LinkSession& session, return false; } - if (!runtime::applyResourceHashmapUpdate(*resource, - update.segment, - update.hashmap, - segment_capacity, - millis())) + if (!link_manager_.applyIncomingResourceHashmapUpdate(*resource, + update.segment, + update.hashmap.data(), + update.hashmap.size(), + segment_capacity, + millis())) { return false; } @@ -6394,358 +5897,372 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session, } bool saw_incoming_resource = false; - for (auto& resource : session.incoming_resources) - { - if (resource.complete) + bool handled_resource_part = false; + link_manager_.forEachIncomingResource( + session, + [&](LinkResourceTransfer& resource) -> bool { - continue; - } - saw_incoming_resource = true; + if (resource.complete) + { + return true; + } + saw_incoming_resource = true; - uint8_t full_hash[reticulum::kFullHashSize] = {}; - if (!fullHashJoined(packet.payload, - packet.payload_len, - resource.random_hash, - sizeof(resource.random_hash), - full_hash)) - { - return false; - } + uint8_t full_hash[reticulum::kFullHashSize] = {}; + if (!fullHashJoined(packet.payload, + packet.payload_len, + resource.random_hash, + sizeof(resource.random_hash), + full_hash)) + { + return false; + } + + bool complete = false; + std::size_t matched_index = resource.part_count; + if (!link_manager_.recordIncomingResourcePart(resource, + packet.payload, + packet.payload_len, + full_hash, + millis(), + &matched_index, + &complete)) + { + if (session.destination == LocalDestinationKind::NomadPage) + { + char resource_prefix[9] = {}; + char part_hash[9] = {}; + char first_expected[9] = {}; + uint32_t known_count = 0; + formatHashPrefix(resource.resource_hash, + resource_prefix, + sizeof(resource_prefix)); + formatHashPrefix(full_hash, part_hash, sizeof(part_hash)); + for (std::size_t index = 0; index < resource.map_hash_known.size(); ++index) + { + known_count += resource.map_hash_known[index] != 0 ? 1U : 0U; + } + if (!resource.map_hashes.empty()) + { + formatHashPrefix(resource.map_hashes.front().data(), + first_expected, + sizeof(first_expected)); + } + Serial.printf("[LXMF][ResourceRX] part_unmatched resource=%s part_hash=%s first_expected=%s known=%u/%u payload_len=%u\n", + resource_prefix, + part_hash, + first_expected[0] != '\0' ? first_expected : "-", + static_cast(known_count), + static_cast(resource.part_count), + static_cast(packet.payload_len)); + } + return true; + } - bool complete = false; - std::size_t matched_index = resource.part_count; - if (!runtime::recordResourcePart(resource, - packet.payload, - packet.payload_len, - full_hash, - millis(), - &matched_index, - &complete)) - { if (session.destination == LocalDestinationKind::NomadPage) { char resource_prefix[9] = {}; char part_hash[9] = {}; - char first_expected[9] = {}; - uint32_t known_count = 0; - formatHashPrefix(resource.resource_hash, - resource_prefix, - sizeof(resource_prefix)); + formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); formatHashPrefix(full_hash, part_hash, sizeof(part_hash)); - for (std::size_t index = 0; index < resource.map_hash_known.size(); ++index) - { - known_count += resource.map_hash_known[index] != 0 ? 1U : 0U; - } - if (!resource.map_hashes.empty()) - { - formatHashPrefix(resource.map_hashes.front().data(), - first_expected, - sizeof(first_expected)); - } - Serial.printf("[LXMF][ResourceRX] part_unmatched resource=%s part_hash=%s first_expected=%s known=%u/%u payload_len=%u\n", + Serial.printf("[LXMF][ResourceRX] part_accept resource=%s part_hash=%s index=%u/%u payload_len=%u complete=%u\n", resource_prefix, part_hash, - first_expected[0] != '\0' ? first_expected : "-", - static_cast(known_count), + static_cast(matched_index), static_cast(resource.part_count), - static_cast(packet.payload_len)); + static_cast(packet.payload_len), + complete ? 1U : 0U); } - continue; - } - if (session.destination == LocalDestinationKind::NomadPage) - { - char resource_prefix[9] = {}; - char part_hash[9] = {}; - formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); - formatHashPrefix(full_hash, part_hash, sizeof(part_hash)); - Serial.printf("[LXMF][ResourceRX] part_accept resource=%s part_hash=%s index=%u/%u payload_len=%u complete=%u\n", - resource_prefix, - part_hash, - static_cast(matched_index), - static_cast(resource.part_count), - static_cast(packet.payload_len), - complete ? 1U : 0U); - } - - if (session.destination == LocalDestinationKind::NomadPage && - !resource.request_id.empty()) - { - if (PendingNomadPageRequest* page_request = - findPendingNomadPageRequestById( - session.remote_destination_hash, - resource.request_id.data(), - resource.request_id.size())) + if (session.destination == LocalDestinationKind::NomadPage && + !resource.request_id.empty()) { - uint32_t received_count = 0; - for (uint8_t received : resource.received_bitmap) + if (PendingNomadPageRequest* page_request = + findPendingNomadPageRequestById( + session.remote_destination_hash, + resource.request_id.data(), + resource.request_id.size())) { - received_count += received != 0 ? 1U : 0U; + uint32_t received_count = 0; + for (uint8_t received : resource.received_bitmap) + { + received_count += received != 0 ? 1U : 0U; + } + const uint32_t total_count = resource.part_count != 0 + ? resource.part_count + : 1U; + int progress_percent = + 45 + static_cast((received_count * 45U) / total_count); + if (progress_percent > 90) + { + progress_percent = 90; + } + char detail[40] = {}; + std::snprintf(detail, + sizeof(detail), + "%u/%u parts", + static_cast(received_count), + static_cast(resource.part_count)); + updateNomadPageProgress(*page_request, + complete ? 90 : progress_percent, + "Receiving Nomad page", + detail, + true, + false, + PageFailureKind::None); } - const uint32_t total_count = resource.part_count != 0 - ? resource.part_count - : 1U; - int progress_percent = - 45 + static_cast((received_count * 45U) / total_count); - if (progress_percent > 90) - { - progress_percent = 90; - } - char detail[40] = {}; - std::snprintf(detail, - sizeof(detail), - "%u/%u parts", - static_cast(received_count), - static_cast(resource.part_count)); - updateNomadPageProgress(*page_request, - complete ? 90 : progress_percent, - "Receiving Nomad page", - detail, - true, - false, - PageFailureKind::None); } - } - if (!complete) - { - (void)requestNextResourceWindow(session, resource); - return true; - } - - runtime::ResourcePayloadBuffer assembled; - assembled.reserve(resource.transfer_size); - for (const auto& part : resource.parts) - { - assembled.insert(assembled.end(), part.begin(), part.end()); - } - if (assembled.size() > resource.transfer_size) - { - assembled.resize(resource.transfer_size); - } - - runtime::ResourcePayloadBuffer resource_stream; - if (resource.encrypted) - { - if (!decryptLinkPayload(session, - assembled.data(), - assembled.size(), - &resource_stream)) + if (!complete) { + (void)requestNextResourceWindow(session, resource); + handled_resource_part = true; return false; } - } - else - { - resource_stream = std::move(assembled); - } - if (resource.has_metadata || resource_stream.size() < kResourceDataPrefixLen) - { - char resource_prefix[9] = {}; - formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); - Serial.printf("[LXMF][ResourceRX] assemble_reject resource=%s reason=%s stream=%u\n", - resource_prefix, - resource.has_metadata ? "metadata" : "short_stream", - static_cast(resource_stream.size())); - return false; - } + runtime::ResourcePayloadBuffer assembled; + assembled.reserve(resource.transfer_size); + for (const auto& part : resource.parts) + { + assembled.insert(assembled.end(), part.begin(), part.end()); + } + if (assembled.size() > resource.transfer_size) + { + assembled.resize(resource.transfer_size); + } - const uint8_t* resource_payload = - resource_stream.data() + kResourceDataPrefixLen; - const size_t resource_payload_len = - resource_stream.size() - kResourceDataPrefixLen; - runtime::ResourcePayloadBuffer payload_data; - if (resource.compressed) - { - int bz_status = BZ_OK; - if (!decompressBzip2Payload(resource_payload, - resource_payload_len, - resource.data_size, - &payload_data, - &bz_status)) + runtime::ResourcePayloadBuffer resource_stream; + if (resource.encrypted) + { + if (!decryptLinkPayload(session, + assembled.data(), + assembled.size(), + &resource_stream)) + { + return false; + } + } + else + { + resource_stream = std::move(assembled); + } + + if (resource.has_metadata || resource_stream.size() < kResourceDataPrefixLen) { char resource_prefix[9] = {}; formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); - Serial.printf("[LXMF][ResourceRX] decompress_failed resource=%s bz=%d comp_len=%u " - "expected=%u stream=%u\n", + Serial.printf("[LXMF][ResourceRX] assemble_reject resource=%s reason=%s stream=%u\n", resource_prefix, - bz_status, - static_cast(resource_payload_len), - static_cast(resource.data_size), + resource.has_metadata ? "metadata" : "short_stream", static_cast(resource_stream.size())); return false; } - if (session.destination == LocalDestinationKind::NomadPage) + + const uint8_t* resource_payload = + resource_stream.data() + kResourceDataPrefixLen; + const size_t resource_payload_len = + resource_stream.size() - kResourceDataPrefixLen; + runtime::ResourcePayloadBuffer payload_data; + if (resource.compressed) + { + int bz_status = BZ_OK; + if (!decompressBzip2Payload(resource_payload, + resource_payload_len, + resource.data_size, + &payload_data, + &bz_status)) + { + char resource_prefix[9] = {}; + formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); + Serial.printf("[LXMF][ResourceRX] decompress_failed resource=%s bz=%d comp_len=%u " + "expected=%u stream=%u\n", + resource_prefix, + bz_status, + static_cast(resource_payload_len), + static_cast(resource.data_size), + static_cast(resource_stream.size())); + return false; + } + if (session.destination == LocalDestinationKind::NomadPage) + { + char resource_prefix[9] = {}; + formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); + Serial.printf("[LXMF][ResourceRX] decompressed resource=%s comp_len=%u out=%u\n", + resource_prefix, + static_cast(resource_payload_len), + static_cast(payload_data.size())); + } + } + else + { + payload_data.assign(resource_stream.begin() + kResourceDataPrefixLen, + resource_stream.end()); + } + if (payload_data.size() != resource.data_size) { char resource_prefix[9] = {}; formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); - Serial.printf("[LXMF][ResourceRX] decompressed resource=%s comp_len=%u out=%u\n", + Serial.printf("[LXMF][ResourceRX] assemble_reject resource=%s reason=size actual=%u expected=%u\n", resource_prefix, - static_cast(resource_payload_len), - static_cast(payload_data.size())); - } - } - else - { - payload_data.assign(resource_stream.begin() + kResourceDataPrefixLen, - resource_stream.end()); - } - if (payload_data.size() != resource.data_size) - { - char resource_prefix[9] = {}; - formatHashPrefix(resource.resource_hash, resource_prefix, sizeof(resource_prefix)); - Serial.printf("[LXMF][ResourceRX] assemble_reject resource=%s reason=size actual=%u expected=%u\n", - resource_prefix, - static_cast(payload_data.size()), - static_cast(resource.data_size)); - return false; - } - - uint8_t expected_resource_hash[reticulum::kFullHashSize] = {}; - if (!fullHashJoined(payload_data.data(), - payload_data.size(), - resource.random_hash, - sizeof(resource.random_hash), - expected_resource_hash)) - { - return false; - } - if (!hashesEqual(expected_resource_hash, - resource.resource_hash, - reticulum::kFullHashSize)) - { - return false; - } - - if (!fullHashJoined(payload_data.data(), - payload_data.size(), - resource.resource_hash, - reticulum::kFullHashSize, - resource.expected_proof)) - { - return false; - } - - std::array proof_payload{}; - memcpy(proof_payload.data(), resource.resource_hash, reticulum::kFullHashSize); - memcpy(proof_payload.data() + reticulum::kFullHashSize, - resource.expected_proof, - reticulum::kFullHashSize); - - const bool is_request = (resource.flags & kResourceFlagRequest) != 0; - const bool is_response = (resource.flags & kResourceFlagResponse) != 0; - const bool single_segment = - !resource.split && resource.total_segments <= 1U; - bool delivery_preaccepted = false; - if (single_segment && !is_request && !is_response && - session.destination == LocalDestinationKind::Delivery) - { - delivery_preaccepted = - acceptVerifiedEnvelope(payload_data.data(), - payload_data.size(), - nullptr, - 0); - if (!delivery_preaccepted) - { - uint8_t rejected_resource_hash[reticulum::kFullHashSize] = {}; - copyHash(rejected_resource_hash, - resource.resource_hash, - sizeof(rejected_resource_hash)); - char resource_prefix[9] = {}; - formatHashPrefix(rejected_resource_hash, - resource_prefix, - sizeof(resource_prefix)); - (void)runtime::eraseLinkResourceByHash( - session.incoming_resources, - rejected_resource_hash); - Serial.printf("[LXMF][ResourceRX] delivery_rejected resource=%s reason=queue_or_envelope\n", - resource_prefix); + static_cast(payload_data.size()), + static_cast(resource.data_size)); return false; } - } - (void)sendLinkPacket(session, - reticulum::PacketType::Proof, - reticulum::PacketContext::ResourcePrf, - proof_payload.data(), - proof_payload.size(), - false); - - runtime::markResourceComplete(resource, millis()); - - const runtime::ResourceAssemblyResult assembly_result = - runtime::appendResourceAssemblySegment(session, resource, payload_data, millis()); - if (assembly_result == runtime::ResourceAssemblyResult::Rejected) - { - return false; - } - if (assembly_result == runtime::ResourceAssemblyResult::WaitingForNextSegment) - { - return true; - } - - if (is_request) - { - DecodedLinkRequest request{}; - if (decodeLinkRequestPayload(payload_data.data(), payload_data.size(), &request)) + uint8_t expected_resource_hash[reticulum::kFullHashSize] = {}; + if (!fullHashJoined(payload_data.data(), + payload_data.size(), + resource.random_hash, + sizeof(resource.random_hash), + expected_resource_hash)) { - uint8_t request_id[reticulum::kTruncatedHashSize] = {}; - reticulum::truncatedHash(payload_data.data(), payload_data.size(), request_id); - if (session.destination == LocalDestinationKind::Propagation) - { - (void)handlePropagationRequest(session, - request, - request_id, - sizeof(request_id)); - } - else - { - (void)sendLinkResponse(session, - request_id, - sizeof(request_id), + return false; + } + if (!hashesEqual(expected_resource_hash, + resource.resource_hash, + reticulum::kFullHashSize)) + { + return false; + } + + if (!fullHashJoined(payload_data.data(), + payload_data.size(), + resource.resource_hash, + reticulum::kFullHashSize, + resource.expected_proof)) + { + return false; + } + + std::array proof_payload{}; + memcpy(proof_payload.data(), resource.resource_hash, reticulum::kFullHashSize); + memcpy(proof_payload.data() + reticulum::kFullHashSize, + resource.expected_proof, + reticulum::kFullHashSize); + + const bool is_request = (resource.flags & kResourceFlagRequest) != 0; + const bool is_response = (resource.flags & kResourceFlagResponse) != 0; + const bool single_segment = + !resource.split && resource.total_segments <= 1U; + bool delivery_preaccepted = false; + if (single_segment && !is_request && !is_response && + session.destination == LocalDestinationKind::Delivery) + { + delivery_preaccepted = + acceptVerifiedEnvelope(payload_data.data(), + payload_data.size(), nullptr, - 0, - true); + 0); + if (!delivery_preaccepted) + { + uint8_t rejected_resource_hash[reticulum::kFullHashSize] = {}; + copyHash(rejected_resource_hash, + resource.resource_hash, + sizeof(rejected_resource_hash)); + char resource_prefix[9] = {}; + formatHashPrefix(rejected_resource_hash, + resource_prefix, + sizeof(resource_prefix)); + (void)link_manager_.eraseIncomingResource( + session, + rejected_resource_hash); + Serial.printf("[LXMF][ResourceRX] delivery_rejected resource=%s reason=queue_or_envelope\n", + resource_prefix); + return false; } } - } - else if (is_response) - { - DecodedLinkResponse response{}; - if (decodeLinkResponsePayload(payload_data.data(), payload_data.size(), &response)) + + (void)sendLinkPacket(session, + reticulum::PacketType::Proof, + reticulum::PacketContext::ResourcePrf, + proof_payload.data(), + proof_payload.size(), + false); + + link_manager_.markResourceComplete(resource, millis()); + + const runtime::ResourceAssemblyResult assembly_result = + link_manager_.appendResourceAssemblySegment(session, + resource, + payload_data, + millis()); + if (assembly_result == runtime::ResourceAssemblyResult::Rejected) { - for (auto& pending : session.pending_requests) + return false; + } + if (assembly_result == runtime::ResourceAssemblyResult::WaitingForNextSegment) + { + handled_resource_part = true; + return false; + } + + if (is_request) + { + DecodedLinkRequest request{}; + if (decodeLinkRequestPayload(payload_data.data(), payload_data.size(), &request)) { - if (pending.request_id == response.request_id) + uint8_t request_id[reticulum::kTruncatedHashSize] = {}; + reticulum::truncatedHash(payload_data.data(), payload_data.size(), request_id); + if (session.destination == LocalDestinationKind::Propagation) { - pending.response_ready = true; - if (!response.data_is_nil) - { - pending.response = std::move(response.packed_data); - } - break; + (void)handlePropagationRequest(session, + request, + request_id, + sizeof(request_id)); + } + else + { + (void)sendLinkResponse(session, + request_id, + sizeof(request_id), + nullptr, + 0, + true); } } } - } - else if (session.destination == LocalDestinationKind::Delivery && - !delivery_preaccepted) - { - (void)acceptVerifiedEnvelope(payload_data.data(), payload_data.size(), nullptr, 0); - } - else if (session.destination == LocalDestinationKind::Propagation) - { - if (rtnet::active().propagation.service_enabled) + else if (is_response) { - (void)handlePropagationBatch(session, - payload_data.data(), - payload_data.size()); + DecodedLinkResponse response{}; + if (decodeLinkResponsePayload(payload_data.data(), payload_data.size(), &response)) + { + for (auto& pending : session.pending_requests) + { + if (pending.request_id.size() == response.request_id.size() && + (pending.request_id.empty() || + std::memcmp(pending.request_id.data(), + response.request_id.data(), + pending.request_id.size()) == 0)) + { + pending.response_ready = true; + if (!response.data_is_nil) + { + pending.response.assign(response.packed_data.begin(), + response.packed_data.end()); + } + break; + } + } + } + } + else if (session.destination == LocalDestinationKind::Delivery && + !delivery_preaccepted) + { + (void)acceptVerifiedEnvelope(payload_data.data(), payload_data.size(), nullptr, 0); + } + else if (session.destination == LocalDestinationKind::Propagation) + { + if (rtnet::active().propagation.service_enabled) + { + (void)handlePropagationBatch(session, + payload_data.data(), + payload_data.size()); + } } - } - return true; - } + handled_resource_part = true; + return false; + }); if (!saw_incoming_resource && session.destination == LocalDestinationKind::NomadPage) { @@ -6755,7 +6272,7 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session, link_hash, static_cast(packet.payload_len)); } - return false; + return handled_resource_part; } bool LxmfAdapter::handleLinkResourceProof(LinkSession& session, @@ -6768,19 +6285,22 @@ bool LxmfAdapter::handleLinkResourceProof(LinkSession& session, const uint8_t* resource_hash = packet.payload; const uint8_t* expected_proof = packet.payload + reticulum::kFullHashSize; - LinkResourceTransfer* resource = runtime::findLinkResource(session.outgoing_resources, resource_hash); + LinkResourceTransfer* resource = + link_manager_.findOutgoingResource(session, resource_hash); if (!resource) { return false; } - if (!runtime::markResourceProofReceived(*resource, expected_proof, millis())) + if (!link_manager_.markOutgoingResourceProofReceived(*resource, + expected_proof, + millis())) { return false; } - const uint32_t message_id = resource->message_id; - resource->message_id = 0; + const uint32_t message_id = + link_manager_.takeResourceMessageId(*resource); if (message_id != 0) { Serial.printf("[LXMF][%s] proof_ok msg=%lu representation=resource\n", @@ -6791,7 +6311,7 @@ bool LxmfAdapter::handleLinkResourceProof(LinkSession& session, const MessageStatus status = session.destination == LocalDestinationKind::Delivery ? MessageStatus::Delivered - : MessageStatus::Sent; + : MessageStatus::Queued; sys::EventBus::publish(new sys::ChatSendResultEvent(message_id, status), 0); } @@ -6827,8 +6347,7 @@ bool LxmfAdapter::handlePropagationBatch(LinkSession& session, kMaxPropagationPeers, 1}; runtime::PropagationBatchAcceptance batch_acceptance{}; - if (!runtime::planPropagationBatchAcceptance(propagation_, - plaintext, + if (!propagation_client_.planBatchAcceptance(plaintext, plaintext_len, batch_context, batch_limits, @@ -6853,8 +6372,7 @@ bool LxmfAdapter::handlePropagationBatch(LinkSession& session, ? nullptr : message.local_delivery_payload.data(), message.local_delivery_payload.size()); - runtime::notePropagationLocalDeliveryResult(propagation_, - message.transient_id, + propagation_client_.noteLocalDeliveryResult(message.transient_id, delivered, currentTimestampSeconds(), kMaxPropagationTransients); @@ -6864,7 +6382,7 @@ bool LxmfAdapter::handlePropagationBatch(LinkSession& session, if (handled) { handled_any = true; - runtime::notePropagationBatchMessageHandled(propagation_, batch_acceptance); + propagation_client_.noteBatchHandled(batch_acceptance); } } @@ -6897,8 +6415,7 @@ bool LxmfAdapter::handlePropagationRequest(LinkSession& session, 24, 16}; runtime::PropagationServiceResponse response{}; - if (!runtime::planPropagationServiceResponse(propagation_, - request, + if (!propagation_client_.planServiceResponse(request, peer_context, currentTimestampSeconds(), limits, @@ -6957,7 +6474,7 @@ bool LxmfAdapter::acceptPropagatedDelivery(const uint8_t* propagated_payload, return false; } - std::vector plaintext(token_len, 0); + runtime::RuntimeByteBuffer plaintext(token_len, 0); size_t plaintext_len = plaintext.size(); if (!reticulum::tokenDecrypt(derived_key, token, @@ -6969,7 +6486,9 @@ bool LxmfAdapter::acceptPropagatedDelivery(const uint8_t* propagated_payload, } plaintext.resize(plaintext_len); - std::vector lxmf_message(reticulum::kTruncatedHashSize + plaintext.size(), 0); + runtime::RuntimeByteBuffer lxmf_message( + reticulum::kTruncatedHashSize + plaintext.size(), + 0); memcpy(lxmf_message.data(), identity_.destinationHash(), reticulum::kTruncatedHashSize); if (!plaintext.empty()) { @@ -7334,7 +6853,7 @@ LxmfAdapter::LinkSession* LxmfAdapter::ensureOutboundLinkSession(PeerInfo& peer, } if (LinkSession* session = - runtime::findOpenLinkSessionByDestination(links_, peer.destination_hash, kind)) + link_manager_.findOpenSessionByDestination(peer.destination_hash, kind)) { return session; } @@ -7346,7 +6865,12 @@ LxmfAdapter::LinkSession* LxmfAdapter::ensureOutboundLinkSession(PeerInfo& peer, path_requested = sendPathRequest(peer); } - LinkSession& session = runtime::appendLinkSession(links_, kMaxLinkSessions); + LinkSession* new_session = link_manager_.appendSession(kMaxLinkSessions); + if (!new_session) + { + return nullptr; + } + LinkSession& session = *new_session; session.created_ms = millis(); session.request_ms = session.created_ms; session.last_inbound_ms = session.created_ms; @@ -7377,7 +6901,7 @@ LxmfAdapter::LinkSession* LxmfAdapter::ensureOutboundLinkSession(PeerInfo& peer, static_cast(peer.node_id), dest_hash, static_cast(kind)); - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return nullptr; } @@ -7387,7 +6911,7 @@ LxmfAdapter::LinkSession* LxmfAdapter::ensureOutboundLinkSession(PeerInfo& peer, static_cast(peer.node_id), dest_hash, static_cast(kind)); - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return nullptr; } @@ -7411,7 +6935,7 @@ LxmfAdapter::LinkSession* LxmfAdapter::ensureOutboundLinkSession(PeerInfo& peer, static_cast(peer.node_id), dest_hash, static_cast(kind)); - links_.sessions.pop_back(); + link_manager_.discardLastSession(); return nullptr; } @@ -7970,25 +7494,30 @@ bool LxmfAdapter::sendCachedPacketReplay(const uint8_t packet_hash[reticulum::kF return false; } - for (const auto& path : transport_.paths) - { - if (path.cached_announce_len == 0) + bool sent = false; + path_manager_.forEachPath( + [this, packet_hash, &sent](const PathEntry& path) { - continue; - } - if (hashesEqual(path.cached_packet_hash, packet_hash, reticulum::kFullHashSize)) - { - return active_ingress_interface_id_ != + if (sent || path.cached_announce_len == 0) + { + return; + } + if (!hashesEqual(path.cached_packet_hash, + packet_hash, + reticulum::kFullHashSize)) + { + return; + } + sent = active_ingress_interface_id_ != reticulum::interfaces::kInvalidInterfaceId ? interfaces_.sendPacketOn(active_ingress_interface_id_, path.cached_announce, path.cached_announce_len) : interfaces_.sendPacket(path.cached_announce, path.cached_announce_len); - } - } + }); - return false; + return sent; } bool LxmfAdapter::shouldProcessWifiIngressPacket(const reticulum::ParsedPacket& packet, @@ -8024,7 +7553,7 @@ bool LxmfAdapter::shouldProcessWifiIngressPacket(const reticulum::ParsedPacket& if (packet.packet_type == reticulum::PacketType::Proof && (findReversePath(packet.destination_hash) || - runtime::findPendingPingReceipt(transport_, packet.destination_hash))) + path_manager_.findPendingPingReceipt(packet.destination_hash))) { return true; } @@ -8131,33 +7660,40 @@ bool LxmfAdapter::isForegroundDiscoveryDestination( return false; } - for (const auto& request : pending_nomad_page_requests_) - { - if (hashesEqual(request.destination_hash, - destination_hash, - reticulum::kTruncatedHashSize)) + bool pending_page = false; + network_page_client_.forEach( + [destination_hash, &pending_page](const PendingNomadPageRequest& request) { - return true; - } + if (pending_page) + { + return; + } + pending_page = hashesEqual(request.destination_hash, + destination_hash, + reticulum::kTruncatedHashSize); + }); + if (pending_page) + { + return true; } - for (const auto& session : links_.sessions) - { - if (session.state == LinkState::Closed || - (session.destination != LocalDestinationKind::CallAudio && - session.destination != LocalDestinationKind::NomadPage)) + bool foreground = false; + link_manager_.forEachSession( + [destination_hash, &foreground](const LinkSession& session) { - continue; - } - if (hashesEqual(session.remote_destination_hash, - destination_hash, - reticulum::kTruncatedHashSize)) - { - return true; - } - } + if (foreground || + session.state == LinkState::Closed || + (session.destination != LocalDestinationKind::CallAudio && + session.destination != LocalDestinationKind::NomadPage)) + { + return; + } + foreground = hashesEqual(session.remote_destination_hash, + destination_hash, + reticulum::kTruncatedHashSize); + }); - return false; + return foreground; } void LxmfAdapter::noteRxSummary(bool wifi_skipped, @@ -8301,100 +7837,101 @@ bool LxmfAdapter::rebroadcastAnnounce(const PathEntry& path, const reticulum::Pa bool LxmfAdapter::isDuplicatePacket(const uint8_t packet_hash[reticulum::kFullHashSize]) { - return runtime::isDuplicatePacket(transport_, packet_hash); + return path_manager_.isDuplicatePacket(packet_hash); } void LxmfAdapter::rememberPacket(const uint8_t packet_hash[reticulum::kFullHashSize]) { - runtime::rememberPacket(transport_, packet_hash, millis(), kMaxPacketFilter); + path_manager_.rememberPacket(packet_hash, millis(), kMaxPacketFilter); } void LxmfAdapter::rememberReversePath(const uint8_t proof_hash[reticulum::kTruncatedHashSize], reticulum::interfaces::InterfaceId interface_id, uint8_t expected_hops) { - runtime::rememberReversePath(transport_, - proof_hash, - interface_id, - expected_hops, - millis(), - kMaxReverseEntries); + path_manager_.rememberReversePath(proof_hash, + interface_id, + expected_hops, + millis(), + kMaxReverseEntries); } LxmfAdapter::ReverseEntry* LxmfAdapter::findReversePath( const uint8_t proof_hash[reticulum::kTruncatedHashSize]) { - return runtime::findReversePath(transport_, proof_hash); + return path_manager_.findReversePath(proof_hash); } LxmfAdapter::PendingPathRequest* LxmfAdapter::findPendingPathRequest( const uint8_t destination_hash[reticulum::kTruncatedHashSize]) { - return runtime::findPendingPathRequest(transport_, destination_hash); + return path_manager_.findPendingPathRequest(destination_hash); } const LxmfAdapter::PendingPathRequest* LxmfAdapter::findPendingPathRequest( const uint8_t destination_hash[reticulum::kTruncatedHashSize]) const { - return runtime::findPendingPathRequest(transport_, destination_hash); + return path_manager_.findPendingPathRequest(destination_hash); } void LxmfAdapter::notePendingPathRequest( const uint8_t destination_hash[reticulum::kTruncatedHashSize], uint32_t now_ms) { - runtime::notePendingPathRequest(transport_, destination_hash, now_ms, kMaxPendingPathRequests); + path_manager_.notePendingPathRequest(destination_hash, now_ms, kMaxPendingPathRequests); } void LxmfAdapter::resolvePendingPathRequest( const uint8_t destination_hash[reticulum::kTruncatedHashSize]) { - runtime::resolvePendingPathRequest(transport_, destination_hash); + path_manager_.resolvePendingPathRequest(destination_hash); } void LxmfAdapter::cullTransportState() { const uint32_t now_ms = millis(); - for (const auto& receipt : transport_.pending_ping_receipts) - { - if (receipt.created_ms == 0 || - (now_ms - receipt.created_ms) <= kPendingPingReceiptTtlMs) + path_manager_.forEachPendingPingReceipt( + [now_ms](const runtime::PendingPingReceipt& receipt) { - continue; - } - char destination_hash[12] = {}; - formatHashPrefix(receipt.destination_hash, - destination_hash, - sizeof(destination_hash)); - Serial.printf("[LXMF][PingRX] timeout dest=%s elapsed_ms=%lu\n", - destination_hash, - static_cast(now_ms - receipt.created_ms)); - sys::EventBus::publish( - new sys::ReticulumPingResultEvent( - receipt.destination_hash, - sys::ReticulumPingResult::Timeout, - now_ms - receipt.created_ms), - 100); - } + if (receipt.created_ms == 0 || + (now_ms - receipt.created_ms) <= kPendingPingReceiptTtlMs) + { + return; + } + char destination_hash[12] = {}; + formatHashPrefix(receipt.destination_hash, + destination_hash, + sizeof(destination_hash)); + Serial.printf("[LXMF][PingRX] timeout dest=%s elapsed_ms=%lu\n", + destination_hash, + static_cast(now_ms - receipt.created_ms)); + sys::EventBus::publish( + new sys::ReticulumPingResultEvent( + receipt.destination_hash, + sys::ReticulumPingResult::Timeout, + now_ms - receipt.created_ms), + 100); + }); - for (const auto& receipt : transport_.pending_delivery_receipts) - { - if (receipt.created_ms == 0 || - (now_ms - receipt.created_ms) <= - kPendingDeliveryReceiptTtlMs) + path_manager_.forEachPendingDeliveryReceipt( + [now_ms](const runtime::PendingDeliveryReceipt& receipt) { - continue; - } - char destination_hash[12] = {}; - formatHashPrefix(receipt.destination_hash, - destination_hash, - sizeof(destination_hash)); - Serial.printf("[LXMF][DirectTX] proof_timeout msg=%lu dest=%s elapsed_ms=%lu status=sent\n", - static_cast(receipt.message_id), - destination_hash, - static_cast(now_ms - - receipt.created_ms)); - } + if (receipt.created_ms == 0 || + (now_ms - receipt.created_ms) <= + kPendingDeliveryReceiptTtlMs) + { + return; + } + char destination_hash[12] = {}; + formatHashPrefix(receipt.destination_hash, + destination_hash, + sizeof(destination_hash)); + Serial.printf("[LXMF][DirectTX] proof_timeout msg=%lu dest=%s elapsed_ms=%lu status=sent\n", + static_cast(receipt.message_id), + destination_hash, + static_cast(now_ms - + receipt.created_ms)); + }); const runtime::TransportRuntimeLimits limits{ kMaxPaths, @@ -8411,35 +7948,32 @@ void LxmfAdapter::cullTransportState() kPendingPingReceiptTtlMs, kMaxPendingDeliveryReceipts, kPendingDeliveryReceiptTtlMs}; - runtime::cullTransportRuntime(transport_, now_ms, limits); + path_manager_.cull(now_ms, limits); cullLinkSessions(); } LxmfAdapter::PathEntry& LxmfAdapter::upsertPath( const uint8_t destination_hash[reticulum::kTruncatedHashSize]) { - return runtime::upsertPath(transport_, destination_hash, kMaxPaths); + return path_manager_.upsertPath(destination_hash, kMaxPaths); } const LxmfAdapter::PathEntry* LxmfAdapter::findPath( const uint8_t destination_hash[reticulum::kTruncatedHashSize]) const { - const PathEntry* path = runtime::findPath(transport_, destination_hash); - return path && !runtime::pathExpired(*path, millis(), kPathTtlMs) - ? path - : nullptr; + return path_manager_.findPath(destination_hash, millis(), kPathTtlMs); } LxmfAdapter::LinkRelayEntry& LxmfAdapter::upsertLinkRelay( const uint8_t link_id[reticulum::kTruncatedHashSize]) { - return runtime::upsertLinkRelay(transport_, link_id, kMaxLinkRelays); + return path_manager_.upsertLinkRelay(link_id, kMaxLinkRelays); } LxmfAdapter::LinkRelayEntry* LxmfAdapter::findLinkRelay( const uint8_t link_id[reticulum::kTruncatedHashSize]) { - return runtime::findLinkRelay(transport_, link_id); + return path_manager_.findLinkRelay(link_id); } void LxmfAdapter::localDestinationHash(LocalDestinationKind kind, @@ -8788,25 +8322,9 @@ LxmfAdapter::findPendingNomadPageRequestById( const uint8_t* request_id, std::size_t request_id_len) { - if (!destination_hash || !request_id || - request_id_len != reticulum::kTruncatedHashSize) - { - return nullptr; - } - - for (auto& request : pending_nomad_page_requests_) - { - if (hashesEqual(request.destination_hash, - destination_hash, - reticulum::kTruncatedHashSize) && - std::memcmp(request.request_id, - request_id, - reticulum::kTruncatedHashSize) == 0) - { - return &request; - } - } - return nullptr; + return network_page_client_.findByRequestId(destination_hash, + request_id, + request_id_len); } void LxmfAdapter::updateNomadPageProgress( @@ -8847,26 +8365,34 @@ void LxmfAdapter::updateNomadPageProgressForDestination( return; } - for (const auto& request : pending_nomad_page_requests_) - { - if (hashesEqual(request.destination_hash, - destination_hash, - reticulum::kTruncatedHashSize)) + network_page_client_.forEach( + [this, + destination_hash, + progress_percent, + message, + detail, + active, + complete, + failure](const PendingNomadPageRequest& request) { - updateNomadPageProgress(request, - progress_percent, - message, - detail, - active, - complete, - failure); - } - } + if (hashesEqual(request.destination_hash, + destination_hash, + reticulum::kTruncatedHashSize)) + { + updateNomadPageProgress(request, + progress_percent, + message, + detail, + active, + complete, + failure); + } + }); } void LxmfAdapter::completeNomadPageRequest( PendingNomadPageRequest& request, - const std::vector& packed_response) + const runtime::ResourcePayloadBuffer& packed_response) { char destination_text[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; formatHashHex(request.destination_hash, @@ -8874,8 +8400,10 @@ void LxmfAdapter::completeNomadPageRequest( destination_text, sizeof(destination_text)); - std::vector page_body; - if (!decodeMsgpackByteString(packed_response, &page_body)) + runtime::ResourcePayloadBuffer page_body; + if (!decodeMsgpackByteString(packed_response.data(), + packed_response.size(), + &page_body)) { updateNomadPageProgress(request, 100, @@ -8928,17 +8456,22 @@ void LxmfAdapter::completeNomadPageRequest( void LxmfAdapter::pumpNomadPageRequests() { const uint32_t now_ms = millis(); - for (std::size_t index = 0; index < pending_nomad_page_requests_.size();) + for (std::size_t index = 0; index < network_page_client_.size();) { - PendingNomadPageRequest& request = pending_nomad_page_requests_[index]; + PendingNomadPageRequest* current_request = network_page_client_.at(index); + if (!current_request) + { + break; + } + PendingNomadPageRequest& request = *current_request; if (request.created_ms != 0 && (now_ms - request.created_ms) > kNomadPageRequestTtlMs) { LinkSession* open_link = - runtime::findOpenLinkSessionByDestination(links_, - request.destination_hash, - LocalDestinationKind::NomadPage); + link_manager_.findOpenSessionByDestination( + request.destination_hash, + LocalDestinationKind::NomadPage); char timeout_destination[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; char timeout_link[12] = {}; formatHashHex(request.destination_hash, @@ -8982,9 +8515,7 @@ void LxmfAdapter::pumpNomadPageRequests() request.request_sent ? 1U : 0U, request.link_started ? 1U : 0U); } - pending_nomad_page_requests_.erase( - pending_nomad_page_requests_.begin() + - static_cast(index)); + network_page_client_.eraseAt(index); continue; } @@ -9016,9 +8547,7 @@ void LxmfAdapter::pumpNomadPageRequests() if (completed) { - pending_nomad_page_requests_.erase( - pending_nomad_page_requests_.begin() + - static_cast(index)); + network_page_client_.eraseAt(index); continue; } @@ -9095,8 +8624,7 @@ void LxmfAdapter::pumpNomadPageRequests() else { LinkSession* open_link = - runtime::findOpenLinkSessionByDestination( - links_, + link_manager_.findOpenSessionByDestination( request.destination_hash, LocalDestinationKind::NomadPage); if (open_link && @@ -9144,8 +8672,21 @@ void LxmfAdapter::pumpNomadPageRequests() kNomadPageSendRetryMs)) { request.last_attempt_ms = now_ms; - LinkSession& session = - runtime::appendLinkSession(links_, kMaxLinkSessions); + LinkSession* new_session = + link_manager_.appendSession(kMaxLinkSessions); + if (!new_session) + { + updateNomadPageProgress(request, + 10, + "Nomad page link start failed", + request.path, + false, + false, + PageFailureKind::Retryable); + ++index; + continue; + } + LinkSession& session = *new_session; session.created_ms = now_ms; session.request_ms = now_ms; session.last_inbound_ms = now_ms; @@ -9172,7 +8713,7 @@ void LxmfAdapter::pumpNomadPageRequests() sendLinkRequest(session); if (!link_sent) { - links_.sessions.pop_back(); + link_manager_.discardLastSession(); } request.link_started = link_sent; updateNomadPageProgress(request, @@ -9382,7 +8923,7 @@ bool LxmfAdapter::sendLinkResponse(LinkSession& session, bool data_is_nil) { const size_t response_capacity = request_id_len + packed_data_len + 32; - std::vector response_payload(response_capacity, 0); + runtime::ResourcePayloadBuffer response_payload(response_capacity, 0); size_t response_len = response_payload.size(); if (!encodeLinkResponsePayload(request_id, request_id_len, @@ -9513,15 +9054,15 @@ bool LxmfAdapter::queueOutgoingResource(LinkSession& session, const size_t collision_guard = (kResourceWindowSize * 2U) + segment_capacity; LinkResourceTransfer resource{}; - if (!runtime::initialiseOutgoingResourceTransfer(resource, - request_id, - request_id_len, - static_cast(len), - static_cast(encrypted_stream.size()), - static_cast(part_count), - flags | kResourceFlagEncrypted, - millis(), - kResourceWindowSize)) + if (!link_manager_.initialiseOutgoingResource(resource, + request_id, + request_id_len, + static_cast(len), + static_cast(encrypted_stream.size()), + static_cast(part_count), + flags | kResourceFlagEncrypted, + millis(), + kResourceWindowSize)) { return false; } @@ -9600,10 +9141,11 @@ bool LxmfAdapter::queueOutgoingResource(LinkSession& session, return false; } - session.outgoing_resources.push_back(std::move(resource)); - if (!advertiseLinkResource(session, session.outgoing_resources.back(), 0)) + LinkResourceTransfer* queued_resource = + link_manager_.appendOutgoingResource(session, std::move(resource)); + if (!queued_resource || !advertiseLinkResource(session, *queued_resource, 0)) { - session.outgoing_resources.pop_back(); + (void)link_manager_.discardLastOutgoingResource(session); return false; } @@ -9638,17 +9180,16 @@ void LxmfAdapter::closeLinkSession(LinkSession& session, LinkCloseReason reason) } session.pending_packet_receipts.clear(); - for (auto& resource : session.outgoing_resources) - { - if (resource.message_id != 0) + link_manager_.takeTrackedOutgoingResourceMessageIds( + session, + [](uint32_t message_id) { sys::EventBus::publish( - new sys::ChatSendResultEvent(resource.message_id, false), 0); - resource.message_id = 0; - } - } + new sys::ChatSendResultEvent(message_id, false), 0); + }); - const bool transitioned = runtime::closeLinkSession(session, reason, millis()); + const bool transitioned = + link_manager_.closeSession(session, reason, millis()); if (!transitioned) { return; @@ -9667,30 +9208,27 @@ void LxmfAdapter::closeLinkSession(LinkSession& session, LinkCloseReason reason) ::platform::ui::reticulum_call::notify_link_closed(session.link_id); } - transport_.link_relays.erase( - std::remove_if(transport_.link_relays.begin(), - transport_.link_relays.end(), - [&session](const LinkRelayEntry& relay) - { - return hashesEqual(relay.link_id, session.link_id, sizeof(relay.link_id)); - }), - transport_.link_relays.end()); + path_manager_.removeLinkRelay(session.link_id); if ((reason == LinkCloseReason::Timeout || reason == LinkCloseReason::Error) && !isZeroBytes(session.remote_destination_hash, sizeof(session.remote_destination_hash))) { expirePath(session.remote_destination_hash); - for (auto& peer : peers_) - { - if (hashesEqual(peer.destination_hash, - session.remote_destination_hash, - sizeof(peer.destination_hash))) + bool requested = false; + destination_registry_.forEach( + [&](PeerInfo& peer) { + if (requested || + !hashesEqual(peer.destination_hash, + session.remote_destination_hash, + sizeof(peer.destination_hash))) + { + return; + } peer.last_path_request_ms = 0; (void)sendPathRequest(peer); - break; - } - } + requested = true; + }); } } @@ -9748,7 +9286,7 @@ void LxmfAdapter::flushDeferredLinkPayloads(LinkSession& session) { sys::EventBus::publish( new sys::ChatSendResultEvent(deferred.message_id, - MessageStatus::Sent), + MessageStatus::Queued), 0); } @@ -9783,30 +9321,20 @@ void LxmfAdapter::expirePath( return; } - transport_.paths.erase( - std::remove_if(transport_.paths.begin(), - transport_.paths.end(), - [destination_hash](const PathEntry& path) - { - return hashesEqual(path.destination_hash, - destination_hash, - sizeof(path.destination_hash)); - }), - transport_.paths.end()); - resolvePendingPathRequest(destination_hash); + path_manager_.expirePath(destination_hash); } LxmfAdapter::LinkSession* LxmfAdapter::findLinkSession( const uint8_t link_id[reticulum::kTruncatedHashSize]) { - return runtime::findLinkSession(links_, link_id); + return link_manager_.findSession(link_id); } LxmfAdapter::LinkSession* LxmfAdapter::findActiveLinkSessionByDestination( const uint8_t destination_hash[reticulum::kTruncatedHashSize], LocalDestinationKind kind) { - return runtime::findActiveLinkSessionByDestination(links_, destination_hash, kind); + return link_manager_.findActiveSessionByDestination(destination_hash, kind); } void LxmfAdapter::cullLinkSessions() @@ -9825,119 +9353,119 @@ void LxmfAdapter::cullLinkSessions() 5000}; const runtime::ResourceRuntimeLimits resource_limits{kResourceTransferTtlMs}; - for (auto& session : links_.sessions) - { - if (session.destination == LocalDestinationKind::CallAudio && - session.state == LinkState::Active) + link_manager_.forEachSession( + [this, now_ms, &call_snapshot, &limits, &resource_limits](LinkSession& session) { - if (session.call_wire_profile == - ReticulumCallWireProfile::SidebandLxst && - reticulum::lxst::call::phaseTimedOut( - session.lxst_call, - now_ms)) + if (session.destination == LocalDestinationKind::CallAudio && + session.state == LinkState::Active) { - char link_hash[12] = {}; - formatHashPrefix(session.link_id, - link_hash, - sizeof(link_hash)); - Serial.printf("[LXMF][Call] phase_timeout link=%s phase=%s local=%u remote=%u elapsed_ms=%lu\n", - link_hash, - reticulum::lxst::call::phaseName( - session.lxst_call.phase), - static_cast( - session.lxst_call.local_status), - static_cast( - session.lxst_call.remote_status), - static_cast( - now_ms - - session.lxst_call.phase_started_ms)); - (void)dispatchLxstCallEvent( - session, - {reticulum::lxst::call::EventType::Timeout}); - continue; - } - if (session.call_wire_profile == - ReticulumCallWireProfile::MeshChatCallAudio && - !session.initiator && - hashesEqual(call_snapshot.link_id, - session.link_id, - sizeof(session.link_id)) && - call_snapshot.realtime_phase == - ::platform::ui::reticulum_call:: - RealtimePhase::IncomingRinging && - now_ms - call_snapshot.updated_ms >= 60000) - { - (void)sendLinkPacket(session, - reticulum::PacketType::Data, - reticulum::PacketContext::LinkClose, - session.link_id, - sizeof(session.link_id), - true, - true); - closeLinkSession(session, LinkCloseReason::Timeout); - continue; - } - } - - runtime::cullLinkSessionTables(session, now_ms, limits); - session.pending_packet_receipts.erase( - std::remove_if( - session.pending_packet_receipts.begin(), - session.pending_packet_receipts.end(), - [now_ms](const runtime::LinkPacketReceipt& receipt) + if (session.call_wire_profile == + ReticulumCallWireProfile::SidebandLxst && + reticulum::lxst::call::phaseTimedOut( + session.lxst_call, + now_ms)) { - if (now_ms - receipt.created_ms <= - kLinkPacketReceiptTtlMs) - { - return false; - } - if (receipt.message_id != 0) - { - sys::EventBus::publish( - new sys::ChatSendResultEvent(receipt.message_id, - false), - 0); - } - return true; - }), - session.pending_packet_receipts.end()); - for (auto& resource : session.outgoing_resources) - { - if (resource.message_id != 0 && - (resource.last_activity_ms == 0 || - now_ms - resource.last_activity_ms > - kResourceTransferTtlMs)) - { - sys::EventBus::publish( - new sys::ChatSendResultEvent(resource.message_id, false), 0); - resource.message_id = 0; - } - } - runtime::cullLinkResources(session, now_ms, resource_limits); - const runtime::LinkRuntimeMaintenance maintenance = - runtime::advanceLinkSessionLifecycle(session, now_ms, limits); - if (maintenance.close_timeout) - { - closeLinkSession(session, LinkCloseReason::Timeout); - } - else - { - if (maintenance.flush_deferred_payloads) - { - flushDeferredLinkPayloads(session); - } - if (maintenance.send_keepalive) - { - (void)sendLinkKeepalive(session); - } - if (maintenance.marked_stale) - { - runtime::markLinkSessionStale(session); - } - } - } + char link_hash[12] = {}; + formatHashPrefix(session.link_id, + link_hash, + sizeof(link_hash)); + Serial.printf("[LXMF][Call] phase_timeout link=%s phase=%s local=%u remote=%u elapsed_ms=%lu\n", + link_hash, + reticulum::lxst::call::phaseName( + session.lxst_call.phase), + static_cast( + session.lxst_call.local_status), + static_cast( + session.lxst_call.remote_status), + static_cast( + now_ms - + session.lxst_call.phase_started_ms)); + (void)dispatchLxstCallEvent( + session, + {reticulum::lxst::call::EventType::Timeout}); + return; + } - runtime::removeExpiredLinkSessions(links_, now_ms, limits); +#if TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT + if (session.call_wire_profile == + ReticulumCallWireProfile::MeshChatCallAudio && + !session.initiator && + hashesEqual(call_snapshot.link_id, + session.link_id, + sizeof(session.link_id)) && + call_snapshot.realtime_phase == + ::platform::ui::reticulum_call::RealtimePhase::IncomingRinging && + now_ms - call_snapshot.updated_ms >= 60000) + { + (void)sendLinkPacket(session, + reticulum::PacketType::Data, + reticulum::PacketContext::LinkClose, + session.link_id, + sizeof(session.link_id), + true, + true); + closeLinkSession(session, LinkCloseReason::Timeout); + return; + } +#endif + } + + link_manager_.cullSessionTables(session, now_ms, limits); + session.pending_packet_receipts.erase( + std::remove_if( + session.pending_packet_receipts.begin(), + session.pending_packet_receipts.end(), + [now_ms](const runtime::LinkPacketReceipt& receipt) + { + if (now_ms - receipt.created_ms <= + kLinkPacketReceiptTtlMs) + { + return false; + } + if (receipt.message_id != 0) + { + sys::EventBus::publish( + new sys::ChatSendResultEvent(receipt.message_id, + false), + 0); + } + return true; + }), + session.pending_packet_receipts.end()); + link_manager_.takeExpiredOutgoingResourceMessageIds( + session, + now_ms, + kResourceTransferTtlMs, + [](uint32_t message_id) + { + sys::EventBus::publish( + new sys::ChatSendResultEvent(message_id, false), 0); + }); + link_manager_.cullResources(session, now_ms, resource_limits); + const runtime::LinkRuntimeMaintenance maintenance = + link_manager_.advanceSessionLifecycle(session, now_ms, limits); + if (maintenance.close_timeout) + { + closeLinkSession(session, LinkCloseReason::Timeout); + } + else + { + if (maintenance.flush_deferred_payloads) + { + flushDeferredLinkPayloads(session); + } + if (maintenance.send_keepalive) + { + (void)sendLinkKeepalive(session); + } + if (maintenance.marked_stale) + { + link_manager_.markSessionStale(session); + } + } + }); + + link_manager_.removeExpiredSessions(now_ms, limits); } LxmfAdapter::PeerInfo* LxmfAdapter::rememberPeerIdentity( @@ -10045,7 +9573,7 @@ bool LxmfAdapter::acceptVerifiedEnvelopeForDestination( } uint8_t packet_hash[reticulum::kFullHashSize] = {}; reticulum::computePacketHash(raw_packet, raw_len, packet_hash); - runtime::forgetPacket(transport_, packet_hash); + path_manager_.forgetPacket(packet_hash); }; DecodedEnvelope envelope{}; @@ -10091,7 +9619,7 @@ bool LxmfAdapter::acceptVerifiedEnvelopeForDestination( (reticulum::kTruncatedHashSize * 2) + envelope.packed_payload.size() + reticulum::kFullHashSize; - std::vector signed_part(signed_part_required); + runtime::RuntimeByteBuffer signed_part(signed_part_required); size_t signed_part_len = signed_part.size(); uint8_t message_hash[reticulum::kFullHashSize] = {}; if (!buildSignedPart(envelope.destination_hash, @@ -10371,48 +9899,19 @@ bool LxmfAdapter::acceptVerifiedEnvelopeForDestination( LxmfAdapter::PeerInfo* LxmfAdapter::findPeerByNodeId(NodeId node_id) { - for (auto& peer : peers_) - { - if (peer.node_id == node_id) - { - return &peer; - } - } - return nullptr; + return destination_registry_.findByNodeId(node_id); } const LxmfAdapter::PeerInfo* LxmfAdapter::findPeerByDestinationHash( const uint8_t hash[reticulum::kTruncatedHashSize]) const { - if (!hash) - { - return nullptr; - } - for (const auto& peer : peers_) - { - if (hashesEqual(peer.destination_hash, hash, reticulum::kTruncatedHashSize)) - { - return &peer; - } - } - return nullptr; + return destination_registry_.findByDestinationHash(hash); } const LxmfAdapter::PeerInfo* LxmfAdapter::findPeerByIdentityHash( const uint8_t hash[reticulum::kTruncatedHashSize]) const { - if (!hash) - { - return nullptr; - } - for (const auto& peer : peers_) - { - if (hashesEqual(peer.identity_hash, hash, reticulum::kTruncatedHashSize)) - { - return &peer; - } - } - return nullptr; + return destination_registry_.findByIdentityHash(hash); } const ReticulumGroupDestinationConfig* LxmfAdapter::findConfiguredGroupDestination( @@ -10449,19 +9948,7 @@ bool LxmfAdapter::isConfiguredGroupDestination( LxmfAdapter::PeerInfo& LxmfAdapter::upsertPeer( const uint8_t destination_hash[reticulum::kTruncatedHashSize]) { - for (auto& peer : peers_) - { - if (hashesEqual(peer.destination_hash, destination_hash, reticulum::kTruncatedHashSize)) - { - return peer; - } - } - - peers_.push_back(PeerInfo{}); - PeerInfo& peer = peers_.back(); - copyHash(peer.destination_hash, destination_hash, reticulum::kTruncatedHashSize); - peer.node_id = reticulum::nodeIdFromDestinationHash(destination_hash); - return peer; + return destination_registry_.upsertDestination(destination_hash); } LxmfAdapter::PeerInfo* LxmfAdapter::upsertPeerFromDirectoryRecord( @@ -10562,14 +10049,10 @@ LxmfAdapter::PeerInfo* LxmfAdapter::findOrLoadPeerByDestinationHash( { return nullptr; } - for (auto& peer : peers_) + if (PeerInfo* peer = + destination_registry_.findByDestinationHash(destination_hash)) { - if (hashesEqual(peer.destination_hash, - destination_hash, - reticulum::kTruncatedHashSize)) - { - return &peer; - } + return peer; } if (!peer_directory_) { diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter_call.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter_call.cpp index 920fce5d..951b2c70 100644 --- a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter_call.cpp +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter_call.cpp @@ -14,6 +14,10 @@ #include #include +#ifndef TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT +#define TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT 0 +#endif + namespace chat::lxmf { namespace @@ -138,9 +142,10 @@ bool LxmfAdapter::sendLxstSignal(LinkSession& session, return false; } - size_t payload_len = sizeof(call_wire_scratch_); + uint8_t* scratch = lxst_telephony_client_.scratch(); + size_t payload_len = lxst_telephony_client_.scratchCapacity(); if (!reticulum::lxst::encodeSignalling(signal, - call_wire_scratch_, + scratch, &payload_len)) { return false; @@ -148,7 +153,7 @@ bool LxmfAdapter::sendLxstSignal(LinkSession& session, const bool sent = sendLinkPacket(session, reticulum::PacketType::Data, reticulum::PacketContext::None, - call_wire_scratch_, + scratch, payload_len, true, call_admission_control); @@ -346,15 +351,16 @@ bool LxmfAdapter::handleLxstPacket(LinkSession& session, continue; } - size_t normalized_len = sizeof(call_wire_scratch_); + uint8_t* scratch = lxst_telephony_client_.scratch(); + size_t normalized_len = lxst_telephony_client_.scratchCapacity(); if (reticulum::audio_call::encodePayload(frame.codec2_mode, frame.encoded, frame.encoded_len, - call_wire_scratch_, + scratch, &normalized_len) && ::platform::ui::reticulum_call::enqueue_inbound_audio( session.link_id, - call_wire_scratch_, + scratch, normalized_len)) { handled = true; @@ -367,6 +373,7 @@ bool LxmfAdapter::sendCallAudioPacket(LinkSession& session, const uint8_t* payload, size_t payload_len) { +#if TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT if (session.call_wire_profile == ReticulumCallWireProfile::MeshChatCallAudio) { @@ -377,6 +384,7 @@ bool LxmfAdapter::sendCallAudioPacket(LinkSession& session, payload_len, true); } +#endif reticulum::audio_call::DecodedPayload decoded{}; if (!reticulum::audio_call::decodePayload(payload, @@ -388,11 +396,12 @@ bool LxmfAdapter::sendCallAudioPacket(LinkSession& session, return false; } - size_t lxst_len = sizeof(call_wire_scratch_); + uint8_t* scratch = lxst_telephony_client_.scratch(); + size_t lxst_len = lxst_telephony_client_.scratchCapacity(); if (!reticulum::lxst::encodeCodec2Frames(decoded.mode, decoded.encoded, decoded.encoded_len, - call_wire_scratch_, + scratch, &lxst_len)) { return false; @@ -400,7 +409,7 @@ bool LxmfAdapter::sendCallAudioPacket(LinkSession& session, return sendLinkPacket(session, reticulum::PacketType::Data, reticulum::PacketContext::None, - call_wire_scratch_, + scratch, lxst_len, true); } @@ -463,6 +472,7 @@ void LxmfAdapter::pumpReticulumAudioCall() if (call_snapshot.realtime_phase != ::platform::ui::reticulum_call::RealtimePhase::ClosingCall) { +#if TRAIL_MATE_ENABLE_MESHCHAT_CALL_AUDIO_COMPAT if (call_session->call_wire_profile == ReticulumCallWireProfile::MeshChatCallAudio && call_snapshot.accepted && @@ -479,6 +489,10 @@ void LxmfAdapter::pumpReticulumAudioCall() } else if (call_session->call_wire_profile == ReticulumCallWireProfile::SidebandLxst && +#else + if (call_session->call_wire_profile == + ReticulumCallWireProfile::SidebandLxst && +#endif !call_session->initiator && call_snapshot.accepted && call_session->lxst_call.phase == diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_announce_ingestor.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_announce_ingestor.cpp new file mode 100644 index 00000000..6b761635 --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_announce_ingestor.cpp @@ -0,0 +1,601 @@ +/** + * @file lxmf_announce_ingestor.cpp + * @brief Verified announce ingestion owner for embedded LXMF. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_announce_ingestor.h" + +#include "chat/infra/lxmf/lxmf_wire.h" + +#include "platform/esp/common/reticulum_runtime_compat.h" + +#include +#include +#include + +namespace chat::lxmf::runtime +{ +namespace +{ + +constexpr const char* kAnonymousPeerDisplayName = "Anonymous Peer"; +constexpr const char* kAnonymousNodeDisplayName = "Anonymous Node"; + +bool hashesEqual(const uint8_t* a, const uint8_t* b, std::size_t len) +{ + if ((!a || !b) && len != 0) + { + return false; + } + for (std::size_t i = 0; i < len; ++i) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; +} + +void copyHash(uint8_t* out, const uint8_t* in, std::size_t len) +{ + if (!out || !in || len == 0) + { + return; + } + std::memcpy(out, in, len); +} + +void copyCString(char* out, std::size_t out_len, const char* in) +{ + if (!out || out_len == 0) + { + return; + } + out[0] = '\0'; + if (!in) + { + return; + } + const std::size_t max_copy_len = out_len - 1U; + const auto* terminator = + static_cast(std::memchr(in, '\0', max_copy_len)); + const std::size_t copy_len = + terminator ? static_cast(terminator - in) : max_copy_len; + std::memcpy(out, in, copy_len); + out[copy_len] = '\0'; +} + +bool isZeroBytes(const uint8_t* data, std::size_t len) +{ + if (!data) + { + return true; + } + for (std::size_t i = 0; i < len; ++i) + { + if (data[i] != 0) + { + return false; + } + } + return true; +} + +bool peerHasUsableRatchet(const PeerInfo& peer) +{ + return peer.has_ratchet && + !isZeroBytes(peer.ratchet_pub, sizeof(peer.ratchet_pub)); +} + +void formatHashHex(const uint8_t* hash, + std::size_t hash_len, + char* out, + std::size_t out_len) +{ + if (!out || out_len == 0) + { + return; + } + out[0] = '\0'; + if (!hash || hash_len == 0 || out_len < ((hash_len * 2U) + 1U)) + { + std::snprintf(out, out_len, "-"); + return; + } + std::size_t used = 0; + for (std::size_t index = 0; index < hash_len && used + 2U < out_len; ++index) + { + used += static_cast( + std::snprintf(out + used, + out_len - used, + "%02X", + static_cast(hash[index]))); + } +} + +bool copyTextAppDataDisplayName(const uint8_t* data, + std::size_t len, + char* out, + std::size_t out_len) +{ + if (!data || len == 0 || len > 96 || !out || out_len == 0) + { + return false; + } + + std::size_t used = 0; + bool has_visible = false; + for (std::size_t index = 0; index < len; ++index) + { + uint8_t byte = data[index]; + if (byte == '\t' || byte == '\r' || byte == '\n') + { + byte = ' '; + } + else if (byte == 0 || byte < 0x20 || byte == 0x7F) + { + out[0] = '\0'; + return false; + } + + if (used + 1U < out_len) + { + out[used++] = static_cast(byte); + } + if (byte != ' ') + { + has_visible = true; + } + } + while (used != 0 && out[used - 1U] == ' ') + { + --used; + } + out[used] = '\0'; + return has_visible && used != 0; +} + +bool isNameHash(const reticulum::ParsedAnnounce& announce, + const char* app_name, + const char* aspect) +{ + if (!announce.valid || !announce.name_hash || !app_name || !aspect) + { + return false; + } + uint8_t expected_name_hash[reticulum::kNameHashSize] = {}; + reticulum::computeNameHash(app_name, aspect, expected_name_hash); + return hashesEqual(expected_name_hash, + announce.name_hash, + sizeof(expected_name_hash)); +} + +bool isLxmfDeliveryAnnounce(const reticulum::ParsedAnnounce& announce) +{ + return isNameHash(announce, "lxmf", "delivery"); +} + +bool isLxmfPropagationAnnounce(const reticulum::ParsedAnnounce& announce) +{ + return isNameHash(announce, "lxmf", "propagation"); +} + +bool isLxstTelephonyAnnounce(const reticulum::ParsedAnnounce& announce) +{ + return isNameHash(announce, "lxst", "telephony"); +} + +bool isCallAudioAnnounce(const reticulum::ParsedAnnounce& announce) +{ + return isNameHash(announce, "call", "audio") || + isLxstTelephonyAnnounce(announce); +} + +bool isNomadNetworkNodeAnnounce(const reticulum::ParsedAnnounce& announce) +{ + return isNameHash(announce, "nomadnetwork", "node"); +} + +void destinationHashForAspect( + const uint8_t identity_hash[reticulum::kTruncatedHashSize], + const char* aspect, + uint8_t out_hash[reticulum::kTruncatedHashSize]) +{ + if (!identity_hash || !aspect || !out_hash) + { + return; + } + uint8_t name_hash[reticulum::kNameHashSize] = {}; + reticulum::computeNameHash("lxmf", aspect, name_hash); + reticulum::computeDestinationHash(name_hash, identity_hash, out_hash); +} + +const char* pathAnnounceDecisionLabel(PathAnnounceDecision decision) +{ + switch (decision) + { + case PathAnnounceDecision::AcceptNew: + return "new"; + case PathAnnounceDecision::AcceptNewer: + return "newer"; + case PathAnnounceDecision::AcceptExpired: + return "expired"; + case PathAnnounceDecision::RejectReplay: + return "replay"; + case PathAnnounceDecision::RejectStale: + return "stale"; + } + return "unknown"; +} + +} // namespace + +bool AnnounceIngestor::ingest(const uint8_t* raw_packet, + std::size_t raw_len, + const reticulum::ParsedPacket& packet, + const LxmfIdentity& local_identity, + DestinationRegistry& destination_registry, + PathManager& path_manager, + const AnnounceIngestOptions& options, + AnnounceIngestResult* out_result) +{ + if (!out_result) + { + return false; + } + *out_result = AnnounceIngestResult{}; + AnnounceIngestResult& result = *out_result; + + if (!raw_packet || raw_len == 0 || !packet.destination_hash || + packet.destination_type != reticulum::DestinationType::Single || + (packet.context != static_cast(reticulum::PacketContext::None) && + packet.context != + static_cast(reticulum::PacketContext::PathResponse))) + { + result.reason = "invalid_packet"; + return false; + } + + if (!reticulum::parseAnnounce(packet, &result.announce) || + !result.announce.valid) + { + char packet_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; + formatHashHex(packet.destination_hash, + reticulum::kTruncatedHashSize, + packet_hash_hex, + sizeof(packet_hash_hex)); + Serial.printf("[LXMF][AnnounceRX] drop reason=parse_failed dest=%s payload_len=%u\n", + packet_hash_hex, + static_cast(packet.payload_len)); + result.reason = "parse_failed"; + return false; + } + + reticulum::computeIdentityHash(result.announce.public_key, + result.identity_hash); + reticulum::computeDestinationHash(result.announce.name_hash, + result.identity_hash, + result.expected_destination_hash); + if (!hashesEqual(result.expected_destination_hash, + packet.destination_hash, + reticulum::kTruncatedHashSize)) + { + char packet_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; + char expected_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; + formatHashHex(packet.destination_hash, + reticulum::kTruncatedHashSize, + packet_hash_hex, + sizeof(packet_hash_hex)); + formatHashHex(result.expected_destination_hash, + sizeof(result.expected_destination_hash), + expected_hash_hex, + sizeof(expected_hash_hex)); + Serial.printf("[LXMF][AnnounceRX] drop reason=destination_mismatch packet=%s expected=%s\n", + packet_hash_hex, + expected_hash_hex); + result.reason = "destination_mismatch"; + return false; + } + + std::size_t signed_len = 0; + std::memcpy(signed_scratch_ + signed_len, + packet.destination_hash, + reticulum::kTruncatedHashSize); + signed_len += reticulum::kTruncatedHashSize; + std::memcpy(signed_scratch_ + signed_len, + result.announce.public_key, + reticulum::kCombinedPublicKeySize); + signed_len += reticulum::kCombinedPublicKeySize; + std::memcpy(signed_scratch_ + signed_len, + result.announce.name_hash, + reticulum::kNameHashSize); + signed_len += reticulum::kNameHashSize; + std::memcpy(signed_scratch_ + signed_len, result.announce.random_hash, 10); + signed_len += 10; + if (result.announce.has_ratchet && result.announce.ratchet && + result.announce.ratchet_len != 0) + { + if (signed_len + result.announce.ratchet_len > sizeof(signed_scratch_)) + { + result.reason = "signed_data_overflow"; + return false; + } + std::memcpy(signed_scratch_ + signed_len, + result.announce.ratchet, + result.announce.ratchet_len); + signed_len += result.announce.ratchet_len; + } + if (result.announce.app_data_len != 0) + { + if (signed_len + result.announce.app_data_len > sizeof(signed_scratch_)) + { + result.reason = "signed_data_overflow"; + return false; + } + std::memcpy(signed_scratch_ + signed_len, + result.announce.app_data, + result.announce.app_data_len); + signed_len += result.announce.app_data_len; + } + + const uint8_t* sig_pub = + result.announce.public_key + reticulum::kEncryptionPublicKeySize; + if (!LxmfIdentity::verify(sig_pub, + result.announce.signature, + signed_scratch_, + signed_len)) + { + char packet_hash_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; + formatHashHex(packet.destination_hash, + reticulum::kTruncatedHashSize, + packet_hash_hex, + sizeof(packet_hash_hex)); + Serial.printf("[LXMF][AnnounceRX] drop reason=signature_failed dest=%s\n", + packet_hash_hex); + result.reason = "signature_failed"; + return false; + } + + if (options.resolve_local_destination) + { + result.local_destination = + options.resolve_local_destination(options.local_destination_context, + packet.destination_hash, + &result.local_kind); + } + if (result.local_destination) + { + result.status = AnnounceIngestResult::Status::Ignored; + result.reason = "local_destination"; + return true; + } + if (packet.hops > options.max_transport_hops) + { + Serial.printf("[LXMF][AnnounceRX] ignore reason=max_hops hops=%u\n", + static_cast(packet.hops)); + result.status = AnnounceIngestResult::Status::Ignored; + result.reason = "max_hops"; + return true; + } + + const PathEntry* existing_path = + path_manager.findAnyPath(packet.destination_hash); + result.path_decision = evaluatePathAnnounce(existing_path, + packet.hops, + result.announce.random_hash, + options.now_ms, + options.path_ttl_ms); + if (!pathAnnounceAccepted(result.path_decision)) + { + char destination_hex[(reticulum::kTruncatedHashSize * 2U) + 1U] = {}; + formatHashHex(packet.destination_hash, + reticulum::kTruncatedHashSize, + destination_hex, + sizeof(destination_hex)); + Serial.printf("[LXMF][AnnounceRX] ignore reason=path_%s dest=%s hops=%u previous_hops=%u\n", + pathAnnounceDecisionLabel(result.path_decision), + destination_hex, + static_cast(packet.hops), + static_cast(existing_path ? existing_path->hops : 0)); + result.status = AnnounceIngestResult::Status::Ignored; + result.reason = "path_rejected"; + return true; + } + + result.path = &path_manager.upsertPath(packet.destination_hash, + options.max_paths); + applyPathAnnounce(*result.path, + packet.hops, + result.announce.random_hash, + options.now_ms, + options.now_s); + result.path->interface_id = options.ingress_interface_id; + result.path->direct = (packet.transport_id == nullptr); + path_manager.resolvePendingPathRequest(packet.destination_hash); + if (packet.transport_id) + { + copyHash(result.path->next_hop_transport, + packet.transport_id, + sizeof(result.path->next_hop_transport)); + } + else + { + copyHash(result.path->next_hop_transport, + packet.destination_hash, + sizeof(result.path->next_hop_transport)); + } + if (raw_len <= sizeof(result.path->cached_announce)) + { + std::memcpy(result.path->cached_announce, raw_packet, raw_len); + result.path->cached_announce_len = raw_len; + reticulum::computePacketHash(raw_packet, + raw_len, + result.path->cached_packet_hash); + } + + result.delivery_announce = isLxmfDeliveryAnnounce(result.announce); + result.propagation_announce = isLxmfPropagationAnnounce(result.announce); + result.call_audio_announce = isCallAudioAnnounce(result.announce); + result.lxst_telephony_announce = isLxstTelephonyAnnounce(result.announce); + result.nomad_node_announce = isNomadNetworkNodeAnnounce(result.announce); + result.contact_announce = + result.delivery_announce || result.lxst_telephony_announce; + result.packet_has_ratchet = + result.announce.has_ratchet && + result.announce.ratchet && + result.announce.ratchet_len == reticulum::kRatchetSize && + !isZeroBytes(result.announce.ratchet, result.announce.ratchet_len); + + bool has_stamp_cost = false; + uint8_t stamp_cost = 0; + if (result.call_audio_announce && result.announce.app_data && + result.announce.app_data_len != 0) + { + (void)copyTextAppDataDisplayName(result.announce.app_data, + result.announce.app_data_len, + result.display_name, + sizeof(result.display_name)); + } + else if (result.delivery_announce && result.announce.app_data && + result.announce.app_data_len != 0 && + unpackPeerAnnounceAppData(result.announce.app_data, + result.announce.app_data_len, + result.display_name, + sizeof(result.display_name), + &has_stamp_cost, + &stamp_cost)) + { + (void)has_stamp_cost; + (void)stamp_cost; + } + else if (result.nomad_node_announce && result.announce.app_data && + result.announce.app_data_len != 0) + { + (void)copyTextAppDataDisplayName(result.announce.app_data, + result.announce.app_data_len, + result.display_name, + sizeof(result.display_name)); + } + else if (!(result.delivery_announce || result.propagation_announce || + result.call_audio_announce || result.nomad_node_announce) && + result.announce.app_data && result.announce.app_data_len != 0) + { + (void)copyTextAppDataDisplayName(result.announce.app_data, + result.announce.app_data_len, + result.display_name, + sizeof(result.display_name)); + } + if ((result.delivery_announce || + (result.call_audio_announce && !result.lxst_telephony_announce)) && + result.display_name[0] == '\0') + { + copyCString(result.display_name, + sizeof(result.display_name), + kAnonymousPeerDisplayName); + } + else if (result.nomad_node_announce && result.display_name[0] == '\0') + { + copyCString(result.display_name, + sizeof(result.display_name), + kAnonymousNodeDisplayName); + } + + if (result.contact_announce) + { + uint8_t peer_destination_hash[reticulum::kTruncatedHashSize] = {}; + if (result.delivery_announce) + { + copyHash(peer_destination_hash, + packet.destination_hash, + sizeof(peer_destination_hash)); + } + else + { + destinationHashForAspect(result.identity_hash, + "delivery", + peer_destination_hash); + } + + PeerInfo& peer = destination_registry.upsertDestination(peer_destination_hash); + const uint32_t previous_seen_s = peer.last_seen_s; + const bool delivery_ratchet_available = + result.delivery_announce && result.packet_has_ratchet; + result.ratchet_changed = + result.delivery_announce && + (peerHasUsableRatchet(peer) != delivery_ratchet_available || + (delivery_ratchet_available && + std::memcmp(peer.ratchet_pub, + result.announce.ratchet, + sizeof(peer.ratchet_pub)) != 0)); + result.identity_changed = + isZeroBytes(peer.identity_hash, sizeof(peer.identity_hash)) || + !hashesEqual(peer.identity_hash, + result.identity_hash, + sizeof(peer.identity_hash)) || + std::memcmp(peer.enc_pub, + result.announce.public_key, + sizeof(peer.enc_pub)) != 0 || + std::memcmp(peer.sig_pub, sig_pub, sizeof(peer.sig_pub)) != 0; + result.display_changed = + result.display_name[0] != '\0' && + std::strncmp(peer.display_name, + result.display_name, + sizeof(peer.display_name)) != 0; + + copyHash(peer.identity_hash, + result.identity_hash, + sizeof(peer.identity_hash)); + std::memcpy(peer.enc_pub, result.announce.public_key, sizeof(peer.enc_pub)); + std::memcpy(peer.sig_pub, sig_pub, sizeof(peer.sig_pub)); + peer.last_seen_s = options.now_s; + if (result.delivery_announce) + { + if (delivery_ratchet_available) + { + std::memcpy(peer.ratchet_pub, + result.announce.ratchet, + sizeof(peer.ratchet_pub)); + peer.has_ratchet = true; + peer.ratchet_seen_s = options.now_s; + } + else + { + std::memset(peer.ratchet_pub, 0, sizeof(peer.ratchet_pub)); + peer.has_ratchet = false; + peer.ratchet_seen_s = 0; + } + } + if (result.display_name[0] != '\0') + { + copyCString(peer.display_name, + sizeof(peer.display_name), + result.display_name); + } + else if (peer.display_name[0] == '\0') + { + copyCString(peer.display_name, + sizeof(peer.display_name), + kAnonymousPeerDisplayName); + } + + result.address_refresh_due = + previous_seen_s == 0 || + (options.now_s >= previous_seen_s && + (options.now_s - previous_seen_s) >= + options.directory_address_refresh_interval_s); + result.should_store_address = + options.ingress_interface != + reticulum::interfaces::InterfaceKind::WifiGateway || + result.identity_changed || result.ratchet_changed || + result.display_changed || result.address_refresh_due; + result.learned_peer = &peer; + } + + result.status = AnnounceIngestResult::Status::Accepted; + result.reason = "accepted"; + (void)local_identity; + return true; +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_destination_registry.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_destination_registry.cpp new file mode 100644 index 00000000..33b38440 --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_destination_registry.cpp @@ -0,0 +1,158 @@ +/** + * @file lxmf_destination_registry.cpp + * @brief Destination and identity registry owner for the embedded LXMF runtime. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_destination_registry.h" + +#include + +namespace chat::lxmf::runtime +{ +namespace +{ + +bool hashesEqual(const uint8_t* a, const uint8_t* b, std::size_t len) +{ + if ((!a || !b) && len != 0) + { + return false; + } + for (std::size_t i = 0; i < len; ++i) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; +} + +void copyHash(uint8_t* out, const uint8_t* in, std::size_t len) +{ + if (!out || !in || len == 0) + { + return; + } + std::memcpy(out, in, len); +} + +} // namespace + +std::size_t DestinationRegistry::size() const +{ + return peers_.size(); +} + +void DestinationRegistry::clear() +{ + peers_.clear(); +} + +PeerInfo* DestinationRegistry::findByNodeId(NodeId node_id) +{ + if (node_id == 0) + { + return nullptr; + } + for (auto& peer : peers_) + { + if (peer.node_id == node_id) + { + return &peer; + } + } + return nullptr; +} + +const PeerInfo* DestinationRegistry::findByNodeId(NodeId node_id) const +{ + if (node_id == 0) + { + return nullptr; + } + for (const auto& peer : peers_) + { + if (peer.node_id == node_id) + { + return &peer; + } + } + return nullptr; +} + +PeerInfo* DestinationRegistry::findByDestinationHash( + const uint8_t hash[reticulum::kTruncatedHashSize]) +{ + if (!hash) + { + return nullptr; + } + for (auto& peer : peers_) + { + if (hashesEqual(peer.destination_hash, + hash, + reticulum::kTruncatedHashSize)) + { + return &peer; + } + } + return nullptr; +} + +const PeerInfo* DestinationRegistry::findByDestinationHash( + const uint8_t hash[reticulum::kTruncatedHashSize]) const +{ + if (!hash) + { + return nullptr; + } + for (const auto& peer : peers_) + { + if (hashesEqual(peer.destination_hash, + hash, + reticulum::kTruncatedHashSize)) + { + return &peer; + } + } + return nullptr; +} + +const PeerInfo* DestinationRegistry::findByIdentityHash( + const uint8_t hash[reticulum::kTruncatedHashSize]) const +{ + if (!hash) + { + return nullptr; + } + for (const auto& peer : peers_) + { + if (hashesEqual(peer.identity_hash, + hash, + reticulum::kTruncatedHashSize)) + { + return &peer; + } + } + return nullptr; +} + +PeerInfo& DestinationRegistry::upsertDestination( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) +{ + if (PeerInfo* existing = findByDestinationHash(destination_hash)) + { + return *existing; + } + + peers_.push_back(PeerInfo{}); + PeerInfo& peer = peers_.back(); + copyHash(peer.destination_hash, + destination_hash, + reticulum::kTruncatedHashSize); + peer.node_id = reticulum::nodeIdFromDestinationHash(destination_hash); + return peer; +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_manager.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_manager.cpp new file mode 100644 index 00000000..2e098fe0 --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_manager.cpp @@ -0,0 +1,380 @@ +/** + * @file lxmf_link_manager.cpp + * @brief Link session owner for the embedded LXMF runtime. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_manager.h" + +#include +#include +#include + +namespace chat::lxmf::runtime +{ +namespace +{ + +bool hashesEqual(const uint8_t* a, const uint8_t* b, std::size_t len) +{ + if ((!a || !b) && len != 0) + { + return false; + } + for (std::size_t i = 0; i < len; ++i) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; +} + +} // namespace + +std::size_t LinkManager::size() const +{ + return links_.sessions.size(); +} + +void LinkManager::clear() +{ + links_.sessions.clear(); +} + +LinkSession* LinkManager::findSession( + const uint8_t link_id[reticulum::kTruncatedHashSize]) +{ + return runtime::findLinkSession(links_, link_id); +} + +LinkSession* LinkManager::findOpenSessionByDestination( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + LocalDestinationKind kind) +{ + return runtime::findOpenLinkSessionByDestination(links_, + destination_hash, + kind); +} + +LinkSession* LinkManager::findActiveSessionByDestination( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + LocalDestinationKind kind) +{ + return runtime::findActiveLinkSessionByDestination(links_, + destination_hash, + kind); +} + +bool LinkManager::ensureCapacity( + std::size_t max_link_sessions, + const uint8_t preserve_link_id[reticulum::kTruncatedHashSize]) +{ + if (max_link_sessions == 0 || links_.sessions.size() < max_link_sessions) + { + return true; + } + + auto discard = links_.sessions.begin(); + if (preserve_link_id) + { + discard = std::find_if( + links_.sessions.begin(), + links_.sessions.end(), + [preserve_link_id](const LinkSession& candidate) + { + return !hashesEqual(candidate.link_id, + preserve_link_id, + sizeof(candidate.link_id)); + }); + if (discard == links_.sessions.end()) + { + return false; + } + } + links_.sessions.erase(discard); + return true; +} + +LinkSession* LinkManager::appendSession(std::size_t max_link_sessions) +{ + return appendSessionPreserving(max_link_sessions, nullptr); +} + +LinkSession* LinkManager::appendSessionPreserving( + std::size_t max_link_sessions, + const uint8_t preserve_link_id[reticulum::kTruncatedHashSize]) +{ + if (!ensureCapacity(max_link_sessions, preserve_link_id)) + { + return nullptr; + } + links_.sessions.push_back(LinkSession{}); + return &links_.sessions.back(); +} + +void LinkManager::discardLastSession() +{ + if (!links_.sessions.empty()) + { + links_.sessions.pop_back(); + } +} + +bool LinkManager::closeSession(LinkSession& session, + LinkCloseReason reason, + uint32_t now_ms) +{ + return runtime::closeLinkSession(session, reason, now_ms); +} + +void LinkManager::cullSessionTables(LinkSession& session, + uint32_t now_ms, + const LinkRuntimeLimits& limits) +{ + runtime::cullLinkSessionTables(session, now_ms, limits); +} + +void LinkManager::cullResources(LinkSession& session, + uint32_t now_ms, + const ResourceRuntimeLimits& limits) +{ + runtime::cullLinkResources(session, now_ms, limits); +} + +LinkRuntimeMaintenance LinkManager::advanceSessionLifecycle( + LinkSession& session, + uint32_t now_ms, + const LinkRuntimeLimits& limits) +{ + return runtime::advanceLinkSessionLifecycle(session, now_ms, limits); +} + +void LinkManager::markSessionStale(LinkSession& session) +{ + runtime::markLinkSessionStale(session); +} + +void LinkManager::removeExpiredSessions(uint32_t now_ms, + const LinkRuntimeLimits& limits) +{ + runtime::removeExpiredLinkSessions(links_, now_ms, limits); +} + +LinkResourceTransfer* LinkManager::findIncomingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) +{ + return runtime::findLinkResource(session.incoming_resources, resource_hash); +} + +const LinkResourceTransfer* LinkManager::findIncomingResource( + const LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) const +{ + return runtime::findLinkResource(session.incoming_resources, resource_hash); +} + +LinkResourceTransfer* LinkManager::findOutgoingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) +{ + return runtime::findLinkResource(session.outgoing_resources, resource_hash); +} + +const LinkResourceTransfer* LinkManager::findOutgoingResource( + const LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) const +{ + return runtime::findLinkResource(session.outgoing_resources, resource_hash); +} + +bool LinkManager::eraseIncomingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) +{ + return runtime::eraseLinkResourceByHash(session.incoming_resources, resource_hash); +} + +bool LinkManager::eraseOutgoingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize]) +{ + return runtime::eraseLinkResourceByHash(session.outgoing_resources, resource_hash); +} + +LinkResourceTransfer* LinkManager::startIncomingResource( + LinkSession& session, + const uint8_t resource_hash[reticulum::kFullHashSize], + const uint8_t random_hash[kResourceMapHashLen], + const uint8_t original_hash[reticulum::kFullHashSize], + const uint8_t* request_id, + std::size_t request_id_len, + const uint8_t* hashmap, + std::size_t hashmap_len, + uint32_t data_size, + uint32_t transfer_size, + uint32_t part_count, + uint32_t segment_index, + uint32_t total_segments, + uint8_t flags, + bool encrypted, + bool compressed, + bool has_metadata, + bool split, + uint32_t now_ms, + uint32_t window_size) +{ + LinkResourceTransfer resource{}; + if (!runtime::initialiseIncomingResourceTransfer(resource, + resource_hash, + random_hash, + original_hash, + request_id, + request_id_len, + hashmap, + hashmap_len, + data_size, + transfer_size, + part_count, + segment_index, + total_segments, + flags, + encrypted, + compressed, + has_metadata, + split, + now_ms, + window_size)) + { + return nullptr; + } + session.incoming_resources.push_back(std::move(resource)); + return &session.incoming_resources.back(); +} + +bool LinkManager::initialiseOutgoingResource(LinkResourceTransfer& resource, + const uint8_t* request_id, + std::size_t request_id_len, + uint32_t data_size, + uint32_t transfer_size, + uint32_t part_count, + uint8_t flags, + uint32_t now_ms, + uint32_t window_size) +{ + return runtime::initialiseOutgoingResourceTransfer(resource, + request_id, + request_id_len, + data_size, + transfer_size, + part_count, + flags, + now_ms, + window_size); +} + +LinkResourceTransfer* LinkManager::appendOutgoingResource( + LinkSession& session, + LinkResourceTransfer&& resource) +{ + session.outgoing_resources.push_back(std::move(resource)); + return &session.outgoing_resources.back(); +} + +bool LinkManager::discardLastOutgoingResource(LinkSession& session) +{ + if (session.outgoing_resources.empty()) + { + return false; + } + session.outgoing_resources.pop_back(); + return true; +} + +ResourceWindowRequest LinkManager::buildNextResourceWindowRequest( + const LinkResourceTransfer& resource) const +{ + return runtime::buildNextResourceWindowRequest(resource); +} + +void LinkManager::noteResourceWindowRequested(LinkResourceTransfer& resource, + bool waiting_for_hashmap, + uint32_t now_ms) +{ + runtime::noteResourceWindowRequest(resource, waiting_for_hashmap, now_ms); +} + +bool LinkManager::applyIncomingResourceHashmapUpdate( + LinkResourceTransfer& resource, + uint32_t segment, + const uint8_t* hashmap, + std::size_t hashmap_len, + std::size_t segment_capacity, + uint32_t now_ms) +{ + return runtime::applyResourceHashmapUpdate(resource, + segment, + hashmap, + hashmap_len, + segment_capacity, + now_ms); +} + +bool LinkManager::recordIncomingResourcePart( + LinkResourceTransfer& resource, + const uint8_t* payload, + std::size_t payload_len, + const uint8_t full_hash[reticulum::kFullHashSize], + uint32_t now_ms, + std::size_t* out_matched_index, + bool* out_complete) +{ + return runtime::recordResourcePart(resource, + payload, + payload_len, + full_hash, + now_ms, + out_matched_index, + out_complete); +} + +void LinkManager::markResourceComplete(LinkResourceTransfer& resource, + uint32_t now_ms) +{ + runtime::markResourceComplete(resource, now_ms); +} + +ResourceAssemblyResult LinkManager::appendResourceAssemblySegment( + LinkSession& session, + LinkResourceTransfer& resource, + ResourcePayloadBuffer& payload_data, + uint32_t now_ms) +{ + return runtime::appendResourceAssemblySegment(session, + resource, + payload_data, + now_ms); +} + +bool LinkManager::markOutgoingResourceProofReceived( + LinkResourceTransfer& resource, + const uint8_t expected_proof[reticulum::kFullHashSize], + uint32_t now_ms) +{ + return runtime::markResourceProofReceived(resource, expected_proof, now_ms); +} + +uint32_t LinkManager::takeResourceMessageId(LinkResourceTransfer& resource) +{ + const uint32_t message_id = resource.message_id; + resource.message_id = 0; + return message_id; +} + +void LinkManager::touchResource(LinkResourceTransfer& resource, uint32_t now_ms) +{ + resource.last_activity_ms = now_ms; +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_lxst_telephony_client.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_lxst_telephony_client.cpp new file mode 100644 index 00000000..4a72183e --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_lxst_telephony_client.cpp @@ -0,0 +1,26 @@ +/** + * @file lxmf_lxst_telephony_client.cpp + * @brief Sideband/LXST telephony runtime owner. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_lxst_telephony_client.h" + +namespace chat::lxmf::runtime +{ + +uint8_t* LxstTelephonyClient::scratch() +{ + return scratch_; +} + +const uint8_t* LxstTelephonyClient::scratch() const +{ + return scratch_; +} + +std::size_t LxstTelephonyClient::scratchCapacity() const +{ + return sizeof(scratch_); +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_network_page_client.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_network_page_client.cpp new file mode 100644 index 00000000..7f0d0a66 --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_network_page_client.cpp @@ -0,0 +1,172 @@ +/** + * @file lxmf_network_page_client.cpp + * @brief Pending Nomad/Network page request owner. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_network_page_client.h" + +#include +#include + +namespace chat::lxmf::runtime +{ +namespace +{ + +bool hashesEqual(const uint8_t* a, const uint8_t* b, std::size_t len) +{ + if ((!a || !b) && len != 0) + { + return false; + } + for (std::size_t i = 0; i < len; ++i) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; +} + +bool isZeroBytes(const uint8_t* data, std::size_t len) +{ + if (!data) + { + return true; + } + for (std::size_t i = 0; i < len; ++i) + { + if (data[i] != 0) + { + return false; + } + } + return true; +} + +void copyHash(uint8_t* out, const uint8_t* in, std::size_t len) +{ + if (!out || !in || len == 0) + { + return; + } + std::memcpy(out, in, len); +} + +} // namespace + +std::size_t NetworkPageClient::size() const +{ + return pending_.size(); +} + +bool NetworkPageClient::empty() const +{ + return pending_.empty(); +} + +void NetworkPageClient::clear() +{ + pending_.clear(); +} + +NetworkPageQueueResult NetworkPageClient::queue( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const char* path, + uint32_t now_ms, + std::size_t max_pending, + std::size_t max_path_len, + PendingNomadPageRequest** out_request) +{ + if (out_request) + { + *out_request = nullptr; + } + if (!destination_hash || + isZeroBytes(destination_hash, reticulum::kTruncatedHashSize) || + !path || path[0] == '\0' || + std::strlen(path) >= max_path_len || + max_path_len > sizeof(PendingNomadPageRequest::path)) + { + return NetworkPageQueueResult::Invalid; + } + + for (PendingNomadPageRequest& pending : pending_) + { + if (hashesEqual(pending.destination_hash, + destination_hash, + reticulum::kTruncatedHashSize) && + std::strcmp(pending.path, path) == 0) + { + if (out_request) + { + *out_request = &pending; + } + return NetworkPageQueueResult::Duplicate; + } + } + + if (pending_.size() >= max_pending) + { + return NetworkPageQueueResult::Full; + } + + PendingNomadPageRequest request{}; + copyHash(request.destination_hash, + destination_hash, + sizeof(request.destination_hash)); + std::snprintf(request.path, sizeof(request.path), "%s", path); + request.created_ms = now_ms; + pending_.push_back(request); + if (out_request) + { + *out_request = &pending_.back(); + } + return NetworkPageQueueResult::Queued; +} + +PendingNomadPageRequest* NetworkPageClient::at(std::size_t index) +{ + return index < pending_.size() ? &pending_[index] : nullptr; +} + +const PendingNomadPageRequest* NetworkPageClient::at(std::size_t index) const +{ + return index < pending_.size() ? &pending_[index] : nullptr; +} + +void NetworkPageClient::eraseAt(std::size_t index) +{ + if (index < pending_.size()) + { + pending_.erase(pending_.begin() + static_cast(index)); + } +} + +PendingNomadPageRequest* NetworkPageClient::findByRequestId( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const uint8_t* request_id, + std::size_t request_id_len) +{ + if (!destination_hash || !request_id || + request_id_len != reticulum::kTruncatedHashSize) + { + return nullptr; + } + for (PendingNomadPageRequest& request : pending_) + { + if (hashesEqual(request.destination_hash, + destination_hash, + reticulum::kTruncatedHashSize) && + std::memcmp(request.request_id, + request_id, + reticulum::kTruncatedHashSize) == 0) + { + return &request; + } + } + return nullptr; +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_packet_router.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_packet_router.cpp new file mode 100644 index 00000000..44bd308e --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_packet_router.cpp @@ -0,0 +1,28 @@ +/** + * @file lxmf_packet_router.cpp + * @brief Single routing decision point for Reticulum packets entering LXMF. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_packet_router.h" + +namespace chat::lxmf::runtime +{ + +PacketRoute ReticulumPacketRouter::route(const reticulum::ParsedPacket& packet) const +{ + switch (packet.packet_type) + { + case reticulum::PacketType::Announce: + return PacketRoute::Announce; + case reticulum::PacketType::Proof: + return PacketRoute::Proof; + case reticulum::PacketType::LinkRequest: + return PacketRoute::LinkRequest; + case reticulum::PacketType::Data: + return PacketRoute::Data; + default: + return PacketRoute::LinkOrTransport; + } +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_path_manager.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_path_manager.cpp new file mode 100644 index 00000000..117d91c5 --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_path_manager.cpp @@ -0,0 +1,258 @@ +/** + * @file lxmf_path_manager.cpp + * @brief Path, packet-filter, proof-route, receipt, and link-relay owner. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_path_manager.h" + +#include +#include + +namespace chat::lxmf::runtime +{ +namespace +{ + +bool hashesEqual(const uint8_t* a, const uint8_t* b, std::size_t len) +{ + if ((!a || !b) && len != 0) + { + return false; + } + for (std::size_t i = 0; i < len; ++i) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; +} + +} // namespace + +bool PathManager::isDuplicatePacket( + const uint8_t packet_hash[reticulum::kFullHashSize]) const +{ + return runtime::isDuplicatePacket(transport_, packet_hash); +} + +void PathManager::rememberPacket( + const uint8_t packet_hash[reticulum::kFullHashSize], + uint32_t now_ms, + std::size_t max_packet_filter) +{ + runtime::rememberPacket(transport_, packet_hash, now_ms, max_packet_filter); +} + +void PathManager::forgetPacket( + const uint8_t packet_hash[reticulum::kFullHashSize]) +{ + runtime::forgetPacket(transport_, packet_hash); +} + +void PathManager::rememberReversePath( + const uint8_t proof_hash[reticulum::kTruncatedHashSize], + uint8_t interface_id, + uint8_t expected_hops, + uint32_t now_ms, + std::size_t max_reverse_entries) +{ + runtime::rememberReversePath(transport_, + proof_hash, + interface_id, + expected_hops, + now_ms, + max_reverse_entries); +} + +ReverseEntry* PathManager::findReversePath( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]) +{ + return runtime::findReversePath(transport_, proof_hash); +} + +PendingPathRequest* PathManager::findPendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) +{ + return runtime::findPendingPathRequest(transport_, destination_hash); +} + +const PendingPathRequest* PathManager::findPendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) const +{ + return runtime::findPendingPathRequest(transport_, destination_hash); +} + +void PathManager::notePendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t now_ms, + std::size_t max_pending_path_requests) +{ + runtime::notePendingPathRequest(transport_, + destination_hash, + now_ms, + max_pending_path_requests); +} + +void PathManager::resolvePendingPathRequest( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) +{ + runtime::resolvePendingPathRequest(transport_, destination_hash); +} + +void PathManager::notePendingPingReceipt( + const uint8_t packet_hash[reticulum::kFullHashSize], + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const uint8_t peer_sig_pub[LxmfIdentity::kSigPubKeySize], + uint32_t now_ms, + std::size_t max_pending_ping_receipts) +{ + runtime::notePendingPingReceipt(transport_, + packet_hash, + destination_hash, + peer_sig_pub, + now_ms, + max_pending_ping_receipts); +} + +PendingPingReceipt* PathManager::findPendingPingReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]) +{ + return runtime::findPendingPingReceipt(transport_, proof_hash); +} + +void PathManager::removePendingPingReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]) +{ + runtime::removePendingPingReceipt(transport_, proof_hash); +} + +void PathManager::notePendingDeliveryReceipt( + const uint8_t packet_hash[reticulum::kFullHashSize], + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + const uint8_t peer_sig_pub[LxmfIdentity::kSigPubKeySize], + MessageId message_id, + uint32_t now_ms, + std::size_t max_pending_delivery_receipts) +{ + runtime::notePendingDeliveryReceipt(transport_, + packet_hash, + destination_hash, + peer_sig_pub, + message_id, + now_ms, + max_pending_delivery_receipts); +} + +PendingDeliveryReceipt* PathManager::findPendingDeliveryReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]) +{ + return runtime::findPendingDeliveryReceipt(transport_, proof_hash); +} + +void PathManager::removePendingDeliveryReceipt( + const uint8_t proof_hash[reticulum::kTruncatedHashSize]) +{ + runtime::removePendingDeliveryReceipt(transport_, proof_hash); +} + +PathEntry& PathManager::upsertPath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + std::size_t max_paths) +{ + return runtime::upsertPath(transport_, destination_hash, max_paths); +} + +const PathEntry* PathManager::findPath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t now_ms, + uint32_t path_ttl_ms) const +{ + const PathEntry* path = runtime::findPath(transport_, destination_hash); + return path && !runtime::pathExpired(*path, now_ms, path_ttl_ms) + ? path + : nullptr; +} + +const PathEntry* PathManager::findAnyPath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) const +{ + return runtime::findPath(transport_, destination_hash); +} + +void PathManager::expirePath( + const uint8_t destination_hash[reticulum::kTruncatedHashSize]) +{ + if (!destination_hash) + { + return; + } + transport_.paths.erase( + std::remove_if(transport_.paths.begin(), + transport_.paths.end(), + [destination_hash](const PathEntry& path) + { + return hashesEqual(path.destination_hash, + destination_hash, + sizeof(path.destination_hash)); + }), + transport_.paths.end()); + resolvePendingPathRequest(destination_hash); +} + +void PathManager::clearPaths() +{ + transport_.paths.clear(); +} + +LinkRelayEntry& PathManager::upsertLinkRelay( + const uint8_t link_id[reticulum::kTruncatedHashSize], + std::size_t max_link_relays) +{ + return runtime::upsertLinkRelay(transport_, link_id, max_link_relays); +} + +LinkRelayEntry* PathManager::findLinkRelay( + const uint8_t link_id[reticulum::kTruncatedHashSize]) +{ + return runtime::findLinkRelay(transport_, link_id); +} + +void PathManager::removeLinkRelay( + const uint8_t link_id[reticulum::kTruncatedHashSize]) +{ + if (!link_id) + { + return; + } + transport_.link_relays.erase( + std::remove_if(transport_.link_relays.begin(), + transport_.link_relays.end(), + [link_id](const LinkRelayEntry& relay) + { + return hashesEqual(relay.link_id, + link_id, + sizeof(relay.link_id)); + }), + transport_.link_relays.end()); +} + +void PathManager::clearReversePathAndRelays() +{ + transport_.reverse_table.clear(); + transport_.pending_path_requests.clear(); + transport_.link_relays.clear(); +} + +void PathManager::cull(uint32_t now_ms, const TransportRuntimeLimits& limits) +{ + runtime::cullTransportRuntime(transport_, now_ms, limits); +} + +void PathManager::clear() +{ + transport_ = TransportRuntime{}; +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_ping_service.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_ping_service.cpp new file mode 100644 index 00000000..da0025b6 --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_ping_service.cpp @@ -0,0 +1,104 @@ +/** + * @file lxmf_ping_service.cpp + * @brief Pending Reticulum ping request owner. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_ping_service.h" + +#include + +namespace chat::lxmf::runtime +{ +namespace +{ + +bool hashesEqual(const uint8_t* a, const uint8_t* b, std::size_t len) +{ + if ((!a || !b) && len != 0) + { + return false; + } + for (std::size_t i = 0; i < len; ++i) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; +} + +void copyHash(uint8_t* out, const uint8_t* in, std::size_t len) +{ + if (!out || !in || len == 0) + { + return; + } + std::memcpy(out, in, len); +} + +bool isZeroBytes(const uint8_t* data, std::size_t len) +{ + if (!data) + { + return true; + } + for (std::size_t i = 0; i < len; ++i) + { + if (data[i] != 0) + { + return false; + } + } + return true; +} + +} // namespace + +std::size_t PingService::size() const +{ + return pending_.size(); +} + +void PingService::clear() +{ + pending_.clear(); +} + +PendingPingQueueResult PingService::queue( + const uint8_t destination_hash[reticulum::kTruncatedHashSize], + uint32_t now_ms, + std::size_t max_pending) +{ + if (!destination_hash || + isZeroBytes(destination_hash, reticulum::kTruncatedHashSize)) + { + return PendingPingQueueResult::Invalid; + } + + for (const PendingPingRequest& pending : pending_) + { + if (hashesEqual(pending.destination_hash, + destination_hash, + sizeof(pending.destination_hash))) + { + return PendingPingQueueResult::Duplicate; + } + } + + if (pending_.size() >= max_pending) + { + return PendingPingQueueResult::Full; + } + + PendingPingRequest request{}; + copyHash(request.destination_hash, + destination_hash, + sizeof(request.destination_hash)); + request.created_ms = now_ms; + request.last_path_request_ms = now_ms; + pending_.push_back(request); + return PendingPingQueueResult::Queued; +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_client.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_client.cpp new file mode 100644 index 00000000..62f9af44 --- /dev/null +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_client.cpp @@ -0,0 +1,570 @@ +/** + * @file lxmf_propagation_client.cpp + * @brief Propagation runtime state owner. + */ + +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_client.h" + +#include +#include +#include +#include + +namespace chat::lxmf::runtime +{ +namespace +{ + +bool bytesEqual(const uint8_t* a, const uint8_t* b, std::size_t len) +{ + return a && b && std::memcmp(a, b, len) == 0; +} + +void copyBytes(uint8_t* out, const uint8_t* in, std::size_t len) +{ + if (out && in && len != 0) + { + std::memcpy(out, in, len); + } +} + +} // namespace + +PropagationRuntime& PropagationClient::state() +{ + return state_; +} + +const PropagationRuntime& PropagationClient::state() const +{ + return state_; +} + +PropagationActivePeerSelection PropagationClient::selectActivePeer( + bool automatic, + const uint8_t configured_hash[reticulum::kTruncatedHashSize], + uint32_t now_s, + uint32_t peer_ttl_s, + bool sync_on_start) +{ + const PropagationPeerState* selected = + selectPropagationPeer(state_, automatic, configured_hash, now_s, peer_ttl_s); + if (!selected) + { + clearActivePeer(); + return {}; + } + + const bool changed = + !state_.has_active_node || + !bytesEqual(state_.active_node_hash, + selected->propagation_hash, + sizeof(state_.active_node_hash)); + copyBytes(state_.active_node_hash, + selected->propagation_hash, + sizeof(state_.active_node_hash)); + state_.has_active_node = true; + if (changed) + { + state_.initial_sync_pending = sync_on_start; + if (!sync_on_start) + { + state_.last_sync_s = now_s; + } + state_.sync_stage = PropagationSyncStage::Idle; + state_.sync_wants.clear(); + state_.sync_haves.clear(); + clearPropagationDeliveryCommits(state_); + state_.persistence_started_ms = 0; + std::memset(state_.sync_request_id, 0, sizeof(state_.sync_request_id)); + } + return PropagationActivePeerSelection{selected, changed}; +} + +void PropagationClient::clearActivePeer() +{ + state_.has_active_node = false; + std::memset(state_.active_node_hash, 0, sizeof(state_.active_node_hash)); +} + +bool PropagationClient::canQueueUpload(std::size_t max_pending) const +{ + return state_.pending_uploads.size() < max_pending; +} + +PendingPropagationUpload* PropagationClient::queueUpload( + PendingPropagationUpload upload, + std::size_t max_pending) +{ + if (!canQueueUpload(max_pending)) + { + return nullptr; + } + + state_.pending_uploads.push_back(std::move(upload)); + return &state_.pending_uploads.back(); +} + +bool PropagationClient::hasPendingUploads() const +{ + return !state_.pending_uploads.empty(); +} + +PendingPropagationUpload* PropagationClient::firstPendingUpload() +{ + return state_.pending_uploads.empty() ? nullptr + : &state_.pending_uploads.front(); +} + +const PendingPropagationUpload* PropagationClient::firstPendingUpload() const +{ + return state_.pending_uploads.empty() ? nullptr + : &state_.pending_uploads.front(); +} + +bool PropagationClient::removeFirstPendingUpload() +{ + if (state_.pending_uploads.empty()) + { + return false; + } + + state_.pending_uploads.erase(state_.pending_uploads.begin()); + return true; +} + +void PropagationClient::markExpiredUploads(uint32_t now_ms, uint32_t ttl_ms) +{ + for (auto& upload : state_.pending_uploads) + { + if (upload.state != PropagationUploadState::Failed && + upload.created_ms != 0 && (now_ms - upload.created_ms) > ttl_ms) + { + upload.state = PropagationUploadState::Failed; + } + } +} + +std::vector PropagationClient::takeFailedUploads() +{ + std::vector failed; + auto& uploads = state_.pending_uploads; + for (auto it = uploads.begin(); it != uploads.end();) + { + if (it->state != PropagationUploadState::Failed) + { + ++it; + continue; + } + failed.push_back(std::move(*it)); + it = uploads.erase(it); + } + return failed; +} + +std::vector PropagationClient::takeAllPendingUploads() +{ + std::vector uploads; + uploads.swap(state_.pending_uploads); + return uploads; +} + +void PropagationClient::resetStampingUploads() +{ +#if !defined(TRAIL_MATE_RETICULUM_PARSE_ONLY) + stamp_.reset(); +#endif + for (auto& upload : state_.pending_uploads) + { + if (upload.state == PropagationUploadState::Stamping) + { + upload.state = PropagationUploadState::NeedsStamp; + } + } +} + +void PropagationClient::resetForDisabled() +{ + state_.sync_wants.clear(); + state_.sync_haves.clear(); + clearPropagationDeliveryCommits(state_); + state_.persistence_started_ms = 0; + state_.sync_started_ms = 0; + state_.sync_stage = PropagationSyncStage::Idle; + state_.initial_sync_pending = true; + clearActivePeer(); + std::memset(state_.sync_request_id, 0, sizeof(state_.sync_request_id)); +#if !defined(TRAIL_MATE_RETICULUM_PARSE_ONLY) + stamp_.reset(); +#endif +} + +void PropagationClient::resetForNetworkConfig(bool sync_on_start) +{ + resetStampingUploads(); + state_.sync_wants.clear(); + state_.sync_haves.clear(); + clearPropagationDeliveryCommits(state_); + state_.persistence_started_ms = 0; + state_.sync_started_ms = 0; + state_.sync_stage = PropagationSyncStage::Idle; + state_.initial_sync_pending = sync_on_start; + clearActivePeer(); + std::memset(state_.sync_request_id, 0, sizeof(state_.sync_request_id)); +} + +bool PropagationClient::syncDue(uint32_t now_s, uint32_t sync_interval_s) const +{ + return state_.initial_sync_pending || + (sync_interval_s != 0 && + (state_.last_sync_s == 0 || now_s < state_.last_sync_s || + (now_s - state_.last_sync_s) >= sync_interval_s)); +} + +PropagationSyncStage PropagationClient::syncStage() const +{ + return state_.sync_stage; +} + +const PropagationIdList& PropagationClient::syncWants() const +{ + return state_.sync_wants; +} + +const PropagationIdList& PropagationClient::syncHaves() const +{ + return state_.sync_haves; +} + +bool PropagationClient::syncHavesEmpty() const +{ + return state_.sync_haves.empty(); +} + +std::size_t PropagationClient::pendingDeliveryCount() const +{ + return state_.pending_deliveries.size(); +} + +bool PropagationClient::startSyncIfDue(uint32_t now_s, + uint32_t now_ms, + uint32_t sync_interval_s) +{ + if (state_.sync_stage != PropagationSyncStage::Idle || + !syncDue(now_s, sync_interval_s)) + { + return false; + } + + state_.sync_stage = PropagationSyncStage::NeedList; + state_.sync_started_ms = now_ms; + state_.persistence_started_ms = 0; + state_.sync_wants.clear(); + state_.sync_haves.clear(); + clearPropagationDeliveryCommits(state_); + std::memset(state_.sync_request_id, 0, sizeof(state_.sync_request_id)); + return true; +} + +void PropagationClient::markSyncRequestSent( + const uint8_t request_id[reticulum::kTruncatedHashSize], + PropagationSyncStage next_stage) +{ + if (request_id) + { + copyBytes(state_.sync_request_id, request_id, sizeof(state_.sync_request_id)); + } + else + { + std::memset(state_.sync_request_id, 0, sizeof(state_.sync_request_id)); + } + state_.sync_stage = next_stage; +} + +bool PropagationClient::syncRequestMatches( + const LinkPendingRequest& request) const +{ + return request.request_id.size() == sizeof(state_.sync_request_id) && + bytesEqual(request.request_id.data(), + state_.sync_request_id, + sizeof(state_.sync_request_id)); +} + +void PropagationClient::markSyncFailed() +{ + state_.sync_stage = PropagationSyncStage::Failed; +} + +void PropagationClient::noteListingResult(const PropagationIdList& remote_ids, + std::size_t max_messages) +{ + const std::size_t limit = std::max(1U, max_messages); + state_.sync_wants.clear(); + state_.sync_haves.clear(); + for (const auto& transient_id : remote_ids) + { + if (transient_id.size() != reticulum::kFullHashSize) + { + continue; + } + if (hasSeenPropagationTransient(state_, transient_id.data(), nullptr)) + { + state_.sync_haves.push_back(transient_id); + } + else if (state_.sync_wants.size() < limit) + { + state_.sync_wants.push_back(transient_id); + } + } + state_.sync_stage = state_.sync_wants.empty() + ? PropagationSyncStage::NeedAcknowledge + : PropagationSyncStage::NeedMessages; +} + +bool PropagationClient::registerDeliveryCommit( + const uint8_t transient_id[reticulum::kFullHashSize], + const uint8_t message_hash[reticulum::kFullHashSize], + std::size_t max_pending) +{ + return awaitPropagationDeliveryCommit(state_, + transient_id, + message_hash, + std::max(1U, max_pending)); +} + +void PropagationClient::rememberDeliveredTransient( + const uint8_t transient_id[reticulum::kFullHashSize], + uint32_t now_s, + std::size_t max_transients) +{ + rememberPropagationTransient(state_, transient_id, true, now_s, max_transients); + const bool already_have = + std::any_of(state_.sync_haves.begin(), + state_.sync_haves.end(), + [transient_id](const auto& have) + { + return have.size() == reticulum::kFullHashSize && + bytesEqual(have.data(), + transient_id, + reticulum::kFullHashSize); + }); + if (!already_have) + { + state_.sync_haves.emplace_back(transient_id, + transient_id + reticulum::kFullHashSize); + } +} + +void PropagationClient::noteDownloadResult(bool registration_failed, + uint32_t now_ms) +{ + state_.sync_stage = + registration_failed ? PropagationSyncStage::Failed + : (state_.pending_deliveries.empty() + ? PropagationSyncStage::NeedAcknowledge + : PropagationSyncStage::AwaitingPersistence); + if (state_.sync_stage == PropagationSyncStage::AwaitingPersistence) + { + state_.persistence_started_ms = now_ms; + } +} + +bool PropagationClient::pollPersistence(uint32_t now_ms, uint32_t ttl_ms) +{ + if (state_.sync_stage != PropagationSyncStage::AwaitingPersistence) + { + return true; + } + if (state_.persistence_started_ms == 0 || + (now_ms - state_.persistence_started_ms) > ttl_ms) + { + state_.sync_stage = PropagationSyncStage::Failed; + return true; + } + if (propagationDeliveryCommitsResolved(state_)) + { + state_.sync_stage = propagationDeliveryCommitRejected(state_) + ? PropagationSyncStage::Failed + : PropagationSyncStage::NeedAcknowledge; + return true; + } + return false; +} + +bool PropagationClient::noteDeliveryCommit( + const uint8_t message_hash[reticulum::kFullHashSize], + bool accepted, + uint32_t now_s, + std::size_t max_transients) +{ + const bool matched = commitPropagationDelivery(state_, + message_hash, + accepted, + now_s, + max_transients); + if (matched && + state_.sync_stage == PropagationSyncStage::AwaitingPersistence && + propagationDeliveryCommitsResolved(state_)) + { + state_.sync_stage = propagationDeliveryCommitRejected(state_) + ? PropagationSyncStage::Failed + : PropagationSyncStage::NeedAcknowledge; + } + return matched; +} + +void PropagationClient::markAcknowledged() +{ + state_.sync_stage = PropagationSyncStage::Complete; +} + +std::size_t PropagationClient::syncHaveCount() const +{ + return state_.sync_haves.size(); +} + +void PropagationClient::finishSyncComplete(uint32_t now_s) +{ + state_.last_sync_s = now_s; + state_.initial_sync_pending = false; + state_.sync_wants.clear(); + state_.sync_haves.clear(); + clearPropagationDeliveryCommits(state_); + state_.persistence_started_ms = 0; + state_.sync_started_ms = 0; + state_.sync_stage = PropagationSyncStage::Idle; + std::memset(state_.sync_request_id, 0, sizeof(state_.sync_request_id)); +} + +void PropagationClient::finishSyncFailed(uint32_t now_s) +{ + state_.sync_wants.clear(); + state_.sync_haves.clear(); + clearPropagationDeliveryCommits(state_); + state_.persistence_started_ms = 0; + state_.sync_started_ms = 0; + state_.sync_stage = PropagationSyncStage::Idle; + state_.initial_sync_pending = false; + state_.last_sync_s = now_s; + std::memset(state_.sync_request_id, 0, sizeof(state_.sync_request_id)); +} + +void PropagationClient::cull(uint32_t now_s, + const PropagationRuntimeLimits& limits) +{ + cullPropagationRuntime(state_, now_s, limits); +} + +const PropagationPeerState* PropagationClient::notePeerAnnounce( + const uint8_t propagation_hash[reticulum::kTruncatedHashSize], + const uint8_t delivery_hash[reticulum::kTruncatedHashSize], + const uint8_t identity_hash[reticulum::kTruncatedHashSize], + uint8_t hops, + const DecodedPropagationAnnounce& announce_data, + const uint8_t* public_key, + uint32_t now_s, + std::size_t max_peers) +{ + PropagationPeerState& peer = upsertPropagationPeer(state_, + propagation_hash, + delivery_hash, + identity_hash, + max_peers); + peer.node_active = announce_data.valid; + peer.hops = hops; + if (announce_data.valid) + { + peer.announce_timebase_s = announce_data.timebase_s; + peer.transfer_limit_kb = announce_data.transfer_limit_kb; + peer.sync_limit_kb = announce_data.sync_limit_kb; + peer.stamp_cost = announce_data.stamp_cost; + peer.stamp_cost_flexibility = announce_data.stamp_cost_flexibility; + peer.peering_cost = announce_data.peering_cost; + if (public_key) + { + std::memcpy(peer.enc_pub, public_key, sizeof(peer.enc_pub)); + std::memcpy(peer.sig_pub, + public_key + sizeof(peer.enc_pub), + sizeof(peer.sig_pub)); + } + std::snprintf(peer.display_name, + sizeof(peer.display_name), + "%s", + announce_data.display_name.c_str()); + } + markPropagationPeerSeen(peer, now_s); + return &peer; +} + +bool PropagationClient::planBatchAcceptance( + const uint8_t* plaintext, + std::size_t plaintext_len, + const PropagationBatchContext& context, + const PropagationBatchLimits& limits, + PropagationBatchAcceptance* out_acceptance) +{ + return planPropagationBatchAcceptance(state_, + plaintext, + plaintext_len, + context, + limits, + out_acceptance); +} + +void PropagationClient::noteLocalDeliveryResult( + const uint8_t transient_id[reticulum::kFullHashSize], + bool delivered, + uint32_t now_s, + std::size_t max_transients) +{ + notePropagationLocalDeliveryResult(state_, + transient_id, + delivered, + now_s, + max_transients); +} + +void PropagationClient::noteBatchHandled( + const PropagationBatchAcceptance& acceptance) +{ + notePropagationBatchMessageHandled(state_, acceptance); +} + +bool PropagationClient::planServiceResponse( + const DecodedLinkRequest& request, + const PropagationServicePeerContext& peer_context, + uint32_t now_s, + const PropagationServiceLimits& limits, + PropagationServiceResponse* out_response) +{ + return planPropagationServiceResponse(state_, + request, + peer_context, + now_s, + limits, + out_response); +} + +#if !defined(TRAIL_MATE_RETICULUM_PARSE_ONLY) +PropagationStampRuntime& PropagationClient::stamp() +{ + return stamp_; +} + +const PropagationStampRuntime& PropagationClient::stamp() const +{ + return stamp_; +} +#endif + +PeerInfo& PropagationClient::peerScratch() +{ + return peer_scratch_; +} + +const PeerInfo& PropagationClient::peerScratch() const +{ + return peer_scratch_; +} + +} // namespace chat::lxmf::runtime diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_runtime.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_runtime.cpp index 333a1ba3..df5eaf24 100644 --- a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_runtime.cpp +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_runtime.cpp @@ -38,7 +38,8 @@ void copyHash(uint8_t* out, const uint8_t* in, std::size_t len) std::memcpy(out, in, len); } -bool validTransientId(const std::vector& transient_id) +template +bool validTransientId(const ByteBuffer& transient_id) { return transient_id.size() == reticulum::kFullHashSize; } @@ -384,7 +385,7 @@ bool commitPropagationDelivery( const bool already_have = std::any_of(propagation.sync_haves.begin(), propagation.sync_haves.end(), - [&pending](const std::vector& transient_id) + [&pending](const auto& transient_id) { return transient_id.size() == reticulum::kFullHashSize && @@ -486,11 +487,11 @@ std::size_t removePropagationEntriesForDestination( return old_size - propagation.entries.size(); } -std::vector> collectMissingPropagationTransientIds( +PropagationIdList collectMissingPropagationTransientIds( const PropagationRuntime& propagation, - const std::vector>& transient_ids) + const PropagationIdList& transient_ids) { - std::vector> wanted_ids; + PropagationIdList wanted_ids; wanted_ids.reserve(transient_ids.size()); for (const auto& transient_id : transient_ids) @@ -509,11 +510,11 @@ std::vector> collectMissingPropagationTransientIds( return wanted_ids; } -std::vector> collectPropagationEntryIdsForDestination( +PropagationIdList collectPropagationEntryIdsForDestination( const PropagationRuntime& propagation, const uint8_t destination_hash[reticulum::kTruncatedHashSize]) { - std::vector> response_items; + PropagationIdList response_items; if (!destination_hash) { return response_items; @@ -535,7 +536,7 @@ std::vector> collectPropagationEntryIdsForDestination( PropagationMessageSelection collectPropagationMessagesForWants( PropagationRuntime& propagation, - const std::vector>& transient_ids, + const PropagationIdList& transient_ids, const uint8_t destination_hash[reticulum::kTruncatedHashSize], std::size_t transfer_limit_bytes, std::size_t base_response_size, diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_service_runtime.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_service_runtime.cpp index 3c4a2053..593d99ce 100644 --- a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_service_runtime.cpp +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_service_runtime.cpp @@ -81,7 +81,7 @@ bool packBoolResponse(bool value, PropagationServiceResponse* out_response) return true; } -bool packIdListResponse(const std::vector>& items, +bool packIdListResponse(const PropagationIdList& items, PropagationServiceResponse* out_response) { if (!out_response) @@ -93,7 +93,10 @@ bool packIdListResponse(const std::vector>& items, 4 + (items.size() * (reticulum::kFullHashSize + 3)); ResourcePayloadBuffer packed(response_capacity, 0); std::size_t packed_len = packed.size(); - if (!encodePropagationIdListPayload(items, packed.data(), &packed_len)) + const RuntimeByteSpanList spans = makeRuntimeByteSpans(items); + if (!encodePropagationIdListPayload(viewRuntimeByteSpans(spans), + packed.data(), + &packed_len)) { return false; } @@ -104,7 +107,7 @@ bool packIdListResponse(const std::vector>& items, return true; } -bool packMessageListResponse(const std::vector& items, +bool packMessageListResponse(const RuntimeByteSpanList& items, PropagationServiceResponse* out_response) { if (!out_response) @@ -119,7 +122,9 @@ bool packMessageListResponse(const std::vector& items, } ResourcePayloadBuffer packed(response_capacity, 0); std::size_t packed_len = packed.size(); - if (!encodePropagationMessageListPayload(items, packed.data(), &packed_len)) + if (!encodePropagationMessageListPayload(viewRuntimeByteSpans(items), + packed.data(), + &packed_len)) { return false; } @@ -140,30 +145,33 @@ bool planOfferResponse(PropagationRuntime& propagation, return packUintResponse(kPropagationErrorInvalidData, out_response); } - DecodedPropagationOffer offer{}; + PropagationIdList transient_ids; + DecodedPropagationOfferHeader offer{}; if (!decodePropagationOfferPayload(request.packed_data.data(), request.packed_data.size(), + appendRuntimeByteBufferCallback, + &transient_ids, &offer)) { return packUintResponse(kPropagationErrorInvalidData, out_response); } - if (offer.peering_key_is_nil || offer.peering_key.empty()) + if (offer.peering_key_is_nil || offer.peering_key.size == 0U) { return packUintResponse(kPropagationErrorInvalidKey, out_response); } out_response->offer_validated = true; - std::vector> wanted_ids = - collectMissingPropagationTransientIds(propagation, offer.transient_ids); + PropagationIdList wanted_ids = + collectMissingPropagationTransientIds(propagation, transient_ids); if (wanted_ids.empty()) { return packBoolResponse(false, out_response); } - if (wanted_ids.size() == offer.transient_ids.size()) + if (wanted_ids.size() == transient_ids.size()) { return packBoolResponse(true, out_response); } @@ -185,9 +193,15 @@ bool planGetResponse(PropagationRuntime& propagation, return packUintResponse(kPropagationErrorInvalidData, out_response); } - DecodedPropagationGetRequest get_request{}; + PropagationIdList wants; + PropagationIdList haves; + DecodedPropagationGetRequestHeader get_request{}; if (!decodePropagationGetRequestPayload(request.packed_data.data(), request.packed_data.size(), + appendRuntimeByteBufferCallback, + &wants, + appendRuntimeByteBufferCallback, + &haves, &get_request)) { return packUintResponse(kPropagationErrorInvalidData, out_response); @@ -195,7 +209,7 @@ bool planGetResponse(PropagationRuntime& propagation, if (!get_request.haves_is_nil) { - for (const auto& transient_id : get_request.haves) + for (const auto& transient_id : haves) { if (transient_id.size() != reticulum::kFullHashSize) { @@ -216,7 +230,7 @@ bool planGetResponse(PropagationRuntime& propagation, if (get_request.wants_is_nil && get_request.haves_is_nil) { - std::vector> response_items = + PropagationIdList response_items = collectPropagationEntryIdsForDestination( propagation, peer_context.remote_delivery_hash); @@ -229,7 +243,7 @@ bool planGetResponse(PropagationRuntime& propagation, : 0U; const PropagationMessageSelection selection = collectPropagationMessagesForWants(propagation, - get_request.wants, + wants, peer_context.remote_delivery_hash, transfer_limit_bytes, limits.base_response_size, @@ -373,15 +387,20 @@ bool planPropagationBatchAcceptance( } PropagationBatchAcceptance acceptance{}; - DecodedPropagationBatch batch{}; - if (!decodePropagationBatch(plaintext, plaintext_len, &batch)) + PropagationMessageList messages; + double remote_timebase = 0.0; + if (!decodePropagationBatch(plaintext, + plaintext_len, + appendRuntimeByteBufferCallback, + &messages, + &remote_timebase)) { *out_acceptance = std::move(acceptance); return false; } if (!context.offer_validated && - batch.messages.size() > limits.max_messages_without_offer) + messages.size() > limits.max_messages_without_offer) { *out_acceptance = std::move(acceptance); return false; @@ -419,8 +438,9 @@ bool planPropagationBatchAcceptance( sizeof(message_context.remote_propagation_hash)); } - acceptance.messages.reserve(batch.messages.size()); - for (const auto& message : batch.messages) + (void)remote_timebase; + acceptance.messages.reserve(messages.size()); + for (const auto& message : messages) { PropagationMessageAcceptance message_acceptance{}; (void)planPropagationMessageAcceptance(propagation, diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_resource_runtime.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_resource_runtime.cpp index 5af19653..b71799f1 100644 --- a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_resource_runtime.cpp +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_resource_runtime.cpp @@ -181,8 +181,10 @@ bool initialiseIncomingResourceTransfer( const uint8_t resource_hash[reticulum::kFullHashSize], const uint8_t random_hash[kResourceMapHashLen], const uint8_t original_hash[reticulum::kFullHashSize], - std::vector&& request_id, - std::vector&& hashmap, + const uint8_t* request_id, + std::size_t request_id_len, + const uint8_t* hashmap, + std::size_t hashmap_len, uint32_t data_size, uint32_t transfer_size, uint32_t part_count, @@ -197,7 +199,8 @@ bool initialiseIncomingResourceTransfer( uint32_t window_size) { if (!resource_hash || !random_hash || !original_hash || part_count == 0 || - hashmap.empty() || (hashmap.size() % kResourceMapHashLen) != 0) + (request_id_len != 0 && !request_id) || !hashmap || hashmap_len == 0 || + (hashmap_len % kResourceMapHashLen) != 0) { return false; } @@ -206,8 +209,11 @@ bool initialiseIncomingResourceTransfer( copyHash(resource.resource_hash, resource_hash, sizeof(resource.resource_hash)); copyHash(resource.random_hash, random_hash, sizeof(resource.random_hash)); copyHash(resource.original_hash, original_hash, sizeof(resource.original_hash)); - resource.request_id = std::move(request_id); - resource.hashmap = std::move(hashmap); + if (request_id_len != 0) + { + resource.request_id.assign(request_id, request_id + request_id_len); + } + resource.hashmap.assign(hashmap, hashmap + hashmap_len); resource.data_size = data_size; resource.transfer_size = transfer_size; resource.part_count = part_count; @@ -342,12 +348,13 @@ void noteResourceWindowRequest(LinkResourceTransfer& resource, bool applyResourceHashmapUpdate(LinkResourceTransfer& resource, uint32_t segment, - const std::vector& hashmap, + const uint8_t* hashmap, + std::size_t hashmap_len, std::size_t segment_capacity, uint32_t now_ms) { - if (segment_capacity == 0 || hashmap.empty() || - (hashmap.size() % kResourceMapHashLen) != 0) + if (segment_capacity == 0 || !hashmap || hashmap_len == 0 || + (hashmap_len % kResourceMapHashLen) != 0) { return false; } @@ -358,14 +365,14 @@ bool applyResourceHashmapUpdate(LinkResourceTransfer& resource, return false; } - const std::size_t hash_count = hashmap.size() / kResourceMapHashLen; + const std::size_t hash_count = hashmap_len / kResourceMapHashLen; const std::size_t applied = std::min(hash_count, static_cast(resource.part_count) - start_index); for (std::size_t index = 0; index < applied; ++index) { std::array map_hash{}; std::memcpy(map_hash.data(), - hashmap.data() + (index * kResourceMapHashLen), + hashmap + (index * kResourceMapHashLen), map_hash.size()); resource.map_hashes[start_index + index] = map_hash; resource.map_hash_known[start_index + index] = 1; diff --git a/platform/esp/arduino_common/src/platform_ui_reticulum_directory_runtime.cpp b/platform/esp/arduino_common/src/platform_ui_reticulum_directory_runtime.cpp index 2fd297b0..97b0025d 100644 --- a/platform/esp/arduino_common/src/platform_ui_reticulum_directory_runtime.cpp +++ b/platform/esp/arduino_common/src/platform_ui_reticulum_directory_runtime.cpp @@ -8,6 +8,8 @@ #include "platform/esp/common/shared_spi_bus_arbiter.h" #include "platform/ui/device_runtime.h" #include "platform/ui/screen_runtime.h" +#elif defined(ESP_PLATFORM) +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #else #include "platform/esp/idf_common/flash_storage_runtime.h" #include @@ -1941,7 +1943,7 @@ namespace platform::ui::reticulum_page namespace { -#if defined(ARDUINO) +#if defined(ARDUINO) || defined(ESP_PLATFORM) constexpr const char* kPagesDir = "/trailmate/reticulum/pages"; #else constexpr const char* kPagesDir = "/fs/trailmate/reticulum/pages"; @@ -2121,8 +2123,10 @@ bool page_sd_available() #if defined(ARDUINO) return ::platform::ui::device::card_ready() && ::platform::esp::arduino_common::storage::sd_card_ready(); +#elif defined(ESP_PLATFORM) + return ::platform::esp::arduino_common::storage::sd_card_ready(); #else - return ::platform::esp::idf_common::flash_storage_runtime::ensure_ready(true); + return false; #endif } @@ -2261,7 +2265,7 @@ std::string cache_path(const char* destination_hash, const char* path) return out; } -#if defined(ARDUINO) +#if defined(ARDUINO) || defined(ESP_PLATFORM) using PageRuntimeFile = ::platform::esp::arduino_common::storage::SdRuntimeFile; @@ -2380,7 +2384,7 @@ bool ensure_dir(const std::string& path) bool ensure_page_parent_dirs(const char* destination_hash, const char* path) { -#if defined(ARDUINO) +#if defined(ARDUINO) || defined(ESP_PLATFORM) if (!ensure_dir("/trailmate") || !ensure_dir("/trailmate/reticulum") || !ensure_dir(kPagesDir)) #else diff --git a/platform/esp/arduino_common/src/platform_ui_reticulum_network_config_runtime.cpp b/platform/esp/arduino_common/src/platform_ui_reticulum_network_config_runtime.cpp index c4c7ae45..c15ebceb 100644 --- a/platform/esp/arduino_common/src/platform_ui_reticulum_network_config_runtime.cpp +++ b/platform/esp/arduino_common/src/platform_ui_reticulum_network_config_runtime.cpp @@ -1,19 +1,15 @@ #include "platform/ui/reticulum_network_config_runtime.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/ui/reticulum_call_runtime.h" #if defined(ARDUINO) -#include "platform/esp/arduino_common/storage/sd_card_runtime.h" - #include #include #else #include "esp_timer.h" #include "nvs.h" #include "platform/esp/idf_common/bsp_runtime.h" - -#include -#include #endif #include "cJSON.h" @@ -828,7 +824,7 @@ cJSON* create_document(const chat::reticulum::ReticulumNetworkConfig& config) return root; } -#if !defined(ARDUINO) +#if !defined(ARDUINO) && !defined(ESP_PLATFORM) void native_path(const char* path, char* out, std::size_t out_len) { const char* mount_point = @@ -853,7 +849,7 @@ bool sd_available() bool sd_exists(const char* path) { -#if defined(ARDUINO) +#if defined(ARDUINO) || defined(ESP_PLATFORM) return ::platform::esp::arduino_common::storage::sd_exists(path); #else char native[192] = {}; @@ -867,7 +863,7 @@ bool sd_exists(const char* path) bool ensure_directory() { -#if defined(ARDUINO) +#if defined(ARDUINO) || defined(ESP_PLATFORM) if (!::platform::esp::arduino_common::storage::sd_exists("/trailmate") && !::platform::esp::arduino_common::storage::sd_mkdir("/trailmate")) { @@ -892,7 +888,7 @@ bool read_sd_file(const char* path, std::size_t* out_len) return false; } *out_len = 0; -#if defined(ARDUINO) +#if defined(ARDUINO) || defined(ESP_PLATFORM) ::platform::esp::arduino_common::storage::SdRuntimeFile file; if (!file.open(path, "r")) { @@ -944,7 +940,7 @@ bool write_sd_file_atomic(const char* text, std::size_t len) { return false; } -#if defined(ARDUINO) +#if defined(ARDUINO) || defined(ESP_PLATFORM) if (::platform::esp::arduino_common::storage::sd_exists(kConfigTempPath)) { ::platform::esp::arduino_common::storage::sd_remove(kConfigTempPath); diff --git a/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp b/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp index 1f9ad1f4..4f4bc3aa 100644 --- a/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp +++ b/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp @@ -1,16 +1,13 @@ #include "platform/ui/settings_backup_runtime.h" -#if defined(ARDUINO) #include "platform/esp/arduino_common/storage/sd_card_runtime.h" +#if defined(ARDUINO) #include #include #else #include "esp_timer.h" #include "nvs.h" #include "platform/esp/idf_common/bsp_runtime.h" - -#include -#include #endif #include @@ -52,70 +49,29 @@ uint32_t uptime_ms() #endif } -#if !defined(ARDUINO) -std::string native_storage_path(const char* path) -{ - const char* mount_point = - ::platform::esp::idf_common::bsp_runtime::sdcard_mount_point(); - return std::string(mount_point ? mount_point : "/sdcard") + (path ? path : ""); -} -#endif - bool storage_exists(const char* path) { -#if defined(ARDUINO) return ::platform::esp::arduino_common::storage::sd_exists(path); -#else - struct stat info - { - }; - const std::string native_path = native_storage_path(path); - return stat(native_path.c_str(), &info) == 0; -#endif } bool storage_is_directory(const char* path) { -#if defined(ARDUINO) return ::platform::esp::arduino_common::storage::sd_is_directory(path); -#else - struct stat info - { - }; - const std::string native_path = native_storage_path(path); - return stat(native_path.c_str(), &info) == 0 && S_ISDIR(info.st_mode); -#endif } bool storage_mkdir(const char* path) { -#if defined(ARDUINO) return ::platform::esp::arduino_common::storage::sd_mkdir(path); -#else - const std::string native_path = native_storage_path(path); - return mkdir(native_path.c_str(), 0775) == 0 || errno == EEXIST; -#endif } bool storage_remove(const char* path) { -#if defined(ARDUINO) return ::platform::esp::arduino_common::storage::sd_remove(path); -#else - const std::string native_path = native_storage_path(path); - return std::remove(native_path.c_str()) == 0 || errno == ENOENT; -#endif } bool storage_rename(const char* from, const char* to) { -#if defined(ARDUINO) return ::platform::esp::arduino_common::storage::sd_rename(from, to); -#else - const std::string native_from = native_storage_path(from); - const std::string native_to = native_storage_path(to); - return std::rename(native_from.c_str(), native_to.c_str()) == 0; -#endif } enum class ValueType : uint8_t @@ -1228,25 +1184,15 @@ bool write_text_atomic(const char* path, const char* temp_path, const char* text { storage_remove(temp_path); } -#if defined(ARDUINO) ::platform::esp::arduino_common::storage::SdRuntimeFile file; if (!file.open(temp_path, "w")) { return false; } const bool wrote = file.write(reinterpret_cast(text), len) == len; + const bool flushed = file.flush(); file.close(); -#else - const std::string native_temp_path = native_storage_path(temp_path); - std::FILE* file = std::fopen(native_temp_path.c_str(), "wb"); - if (!file) - { - return false; - } - const bool wrote = std::fwrite(text, 1, len, file) == len && std::fflush(file) == 0; - std::fclose(file); -#endif - if (!wrote) + if (!wrote || !flushed) { storage_remove(temp_path); return false; @@ -1266,7 +1212,6 @@ bool write_text_atomic(const char* path, const char* temp_path, const char* text bool read_file_text(const char* path, std::string& out) { out.clear(); -#if defined(ARDUINO) ::platform::esp::arduino_common::storage::SdRuntimeFile file; if (!file.open(path, "r")) { @@ -1281,30 +1226,6 @@ bool read_file_text(const char* path, std::string& out) out.resize(size); const std::size_t read = file.read_bytes(&out[0], size); file.close(); -#else - const std::string native_path = native_storage_path(path); - std::FILE* file = std::fopen(native_path.c_str(), "rb"); - if (!file) - { - return false; - } - if (std::fseek(file, 0, SEEK_END) != 0) - { - std::fclose(file); - return false; - } - const long file_size = std::ftell(file); - if (file_size <= 0 || static_cast(file_size) > kMaxBackupBytes || - std::fseek(file, 0, SEEK_SET) != 0) - { - std::fclose(file); - return false; - } - const std::size_t size = static_cast(file_size); - out.resize(size); - const std::size_t read = std::fread(&out[0], 1, size, file); - std::fclose(file); -#endif if (read != size) { out.clear(); diff --git a/platform/esp/arduino_common/src/sstv/sstv_service.cpp b/platform/esp/arduino_common/src/sstv/sstv_service.cpp index 4d5cdc53..a8051f26 100644 --- a/platform/esp/arduino_common/src/sstv/sstv_service.cpp +++ b/platform/esp/arduino_common/src/sstv/sstv_service.cpp @@ -9,15 +9,12 @@ defined(TRAIL_MATE_ESP_BOARD_TAB5) || defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) #if defined(TRAIL_MATE_ESP_BOARD_TAB5) || defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) -#include -#include #include -#include -#include #include "esp_heap_caps.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #if defined(TRAIL_MATE_ESP_BOARD_TAB5) #include "platform/esp/idf_common/tab5_codec_compat.h" @@ -37,6 +34,7 @@ #include #include #include +#include #include "sstv/decode_sstv.h" #include "sstv/sstv_config.h" @@ -49,20 +47,22 @@ class File { public: File() = default; - explicit File(FILE* handle) : handle_(handle) {} File(const File&) = delete; File& operator=(const File&) = delete; - File(File&& other) noexcept : handle_(other.handle_) + File(File&& other) noexcept : file_(other.file_), open_(other.open_) { - other.handle_ = nullptr; + other.file_ = nullptr; + other.open_ = false; } File& operator=(File&& other) noexcept { if (this != &other) { close(); - handle_ = other.handle_; - other.handle_ = nullptr; + file_ = other.file_; + open_ = other.open_; + other.file_ = nullptr; + other.open_ = false; } return *this; } @@ -73,72 +73,96 @@ class File explicit operator bool() const { - return handle_ != nullptr; + return file_ != nullptr && open_; } size_t write(const void* data, size_t size) { - return handle_ ? std::fwrite(data, 1, size, handle_) : 0; + return file_ && open_ ? file_->write(data, size) : 0; } void flush() { - if (handle_) + if (file_ && open_) { - std::fflush(handle_); + (void)file_->flush(); } } void close() { - if (handle_) + if (file_) { - std::fclose(handle_); - handle_ = nullptr; + file_->close(); + delete file_; + file_ = nullptr; + open_ = false; } } + bool open(const char* path, const char* mode) + { + close(); + file_ = new (std::nothrow)::platform::esp::arduino_common::storage::SdRuntimeFile(); + if (!file_) + { + return false; + } + open_ = file_->open(path, mode); + if (!open_) + { + close(); + } + return open_; + } + private: - FILE* handle_ = nullptr; + ::platform::esp::arduino_common::storage::SdRuntimeFile* file_ = nullptr; + bool open_ = false; }; std::string resolve_sd_path(const char* path) { - const std::string mount = platform::esp::idf_common::bsp_runtime::sdcard_mount_point(); if (!path || path[0] == '\0') { - return mount; + return "/"; } - if (std::strncmp(path, mount.c_str(), mount.size()) == 0) + std::string logical = path; + if (logical.size() >= 2 && (logical[0] == 'A' || logical[0] == 'a') && logical[1] == ':') { - return std::string(path); + logical.erase(0, 2); } - if (path[0] == '/') + if (logical.empty()) { - return mount + path; + return "/"; } - return mount + "/" + path; + if (logical.front() != '/') + { + logical.insert(logical.begin(), '/'); + } + return logical; } struct StorageFacade { bool exists(const char* path) const { - struct stat st - { - }; - return ::stat(resolve_sd_path(path).c_str(), &st) == 0; + const std::string resolved = resolve_sd_path(path); + return ::platform::esp::arduino_common::storage::sd_exists(resolved.c_str()); } bool mkdir(const char* path) const { const std::string resolved = resolve_sd_path(path); - return ::mkdir(resolved.c_str(), 0775) == 0 || errno == EEXIST; + return ::platform::esp::arduino_common::storage::sd_mkdir(resolved.c_str()); } int cardType() const { - return platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() ? 1 : 0; + return platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() && + ::platform::esp::arduino_common::storage::sd_card_ready() + ? 1 + : 0; } File open(const char* path, int mode) const @@ -148,7 +172,9 @@ struct StorageFacade return File{}; } const char* open_mode = mode == 1 ? "wb" : "rb"; - return File{std::fopen(resolve_sd_path(path).c_str(), open_mode)}; + File file; + (void)file.open(resolve_sd_path(path).c_str(), open_mode); + return file; } }; diff --git a/platform/esp/idf_common/include/platform/esp/idf_common/app_runtime_support.h b/platform/esp/idf_common/include/platform/esp/idf_common/app_runtime_support.h index ec2ef805..5d1a74e8 100644 --- a/platform/esp/idf_common/include/platform/esp/idf_common/app_runtime_support.h +++ b/platform/esp/idf_common/include/platform/esp/idf_common/app_runtime_support.h @@ -5,6 +5,8 @@ namespace platform::esp::idf_common { +void setLvglTaskOwnedUiDispatch(bool enabled); void tickBoundLifecycle(std::size_t max_events = 32); +void tickLvglTaskOwnedUiLifecycle(std::size_t max_events = 32); } // namespace platform::esp::idf_common diff --git a/platform/esp/idf_common/include/platform/esp/idf_common/bsp_runtime.h b/platform/esp/idf_common/include/platform/esp/idf_common/bsp_runtime.h index 9c7e3824..8ef64c2d 100644 --- a/platform/esp/idf_common/include/platform/esp/idf_common/bsp_runtime.h +++ b/platform/esp/idf_common/include/platform/esp/idf_common/bsp_runtime.h @@ -5,6 +5,7 @@ namespace platform::esp::idf_common::bsp_runtime bool ensure_nvs_ready(); bool ensure_sdcard_ready(); +void mark_sdcard_unmounted(); bool sdcard_ready(); const char* sdcard_mount_point(); bool display_ready(); diff --git a/platform/esp/idf_common/include/platform/esp/idf_common/sd_card_runtime_sdfat_adapter.h b/platform/esp/idf_common/include/platform/esp/idf_common/sd_card_runtime_sdfat_adapter.h new file mode 100644 index 00000000..4dc873c2 --- /dev/null +++ b/platform/esp/idf_common/include/platform/esp/idf_common/sd_card_runtime_sdfat_adapter.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "driver/sdmmc_host.h" +#include "platform/esp/idf_common/sdmmc_host_runtime.h" +#include "sdmmc_cmd.h" + +namespace platform::esp::idf_common::sd_card_runtime +{ + +bool mount_sdmmc(sdmmc_host_runtime::SlotOwner owner, + const sdmmc_host_t& host, + const sdmmc_slot_config_t& slot_config, + const char* mount_point, + uint8_t max_files); + +void unmount_sdmmc(sdmmc_host_runtime::SlotOwner owner); + +sdmmc_card_t* mounted_card(); + +} // namespace platform::esp::idf_common::sd_card_runtime diff --git a/platform/esp/idf_common/include/platform/esp/idf_common/sdmmc_host_runtime.h b/platform/esp/idf_common/include/platform/esp/idf_common/sdmmc_host_runtime.h index 03f7dc62..e3654196 100644 --- a/platform/esp/idf_common/include/platform/esp/idf_common/sdmmc_host_runtime.h +++ b/platform/esp/idf_common/include/platform/esp/idf_common/sdmmc_host_runtime.h @@ -4,7 +4,6 @@ #include "driver/sdmmc_host.h" #include "esp_err.h" -#include "esp_vfs_fat.h" #include "sdmmc_cmd.h" namespace platform::esp::idf_common::sdmmc_host_runtime @@ -29,22 +28,6 @@ struct Snapshot uint8_t usb_mass_storage_host_refs = 0; }; -/** - * Serializes an ESP-IDF FATFS mount with every other SDMMC slot lifecycle - * transition and records both the logical slot owner and the shared Host - * reference owner after the IDF mount succeeds. - */ -esp_err_t mount_fatfs(SlotOwner owner, - const char* mount_point, - const sdmmc_host_t* host, - const sdmmc_slot_config_t* slot_config, - const esp_vfs_fat_mount_config_t* mount_config, - sdmmc_card_t** out_card); - -esp_err_t unmount_fatfs(SlotOwner owner, - const char* mount_point, - sdmmc_card_t* card); - /** * Initializes one non-FATFS SDMMC/SDIO slot under an explicit logical owner. * The matching release_slot() delegates to sdmmc_host_deinit_slot() for the diff --git a/platform/esp/idf_common/src/app_runtime_support.cpp b/platform/esp/idf_common/src/app_runtime_support.cpp index ce63e882..5437c4c0 100644 --- a/platform/esp/idf_common/src/app_runtime_support.cpp +++ b/platform/esp/idf_common/src/app_runtime_support.cpp @@ -4,6 +4,7 @@ #include "esp_log.h" #include "platform/esp/boards/board_runtime.h" +#include #include #include @@ -14,11 +15,33 @@ constexpr const char* kTag = "idf-app-runtime"; constexpr uint32_t kDisplayLockTimeoutMs = 50; constexpr std::size_t kMaxEventsPerDisplayLock = 4; +std::atomic s_lvgl_task_owned_ui_dispatch{false}; + } // namespace namespace platform::esp::idf_common { +void setLvglTaskOwnedUiDispatch(bool enabled) +{ + s_lvgl_task_owned_ui_dispatch.store(enabled, std::memory_order_release); +} + +void tickLvglTaskOwnedUiLifecycle(std::size_t max_events) +{ + if (!app::hasAppFacade()) + { + return; + } + + app::IAppLifecycleFacade& lifecycle = app::lifecycleFacade(); + lifecycle.tickEventRuntime(); + + const std::size_t event_budget = + max_events < kMaxEventsPerDisplayLock ? max_events : kMaxEventsPerDisplayLock; + lifecycle.dispatchPendingEvents(event_budget); +} + void tickBoundLifecycle(std::size_t max_events) { static uint32_t consecutive_lock_timeouts = 0; @@ -32,6 +55,11 @@ void tickBoundLifecycle(std::size_t max_events) // display lock. lifecycle.updateCoreServices(); + if (s_lvgl_task_owned_ui_dispatch.load(std::memory_order_acquire)) + { + return; + } + if (!platform::esp::boards::lockDisplay(kDisplayLockTimeoutMs)) { ++consecutive_lock_timeouts; diff --git a/platform/esp/idf_common/src/bsp_runtime.cpp b/platform/esp/idf_common/src/bsp_runtime.cpp index 843c51aa..706c1d55 100644 --- a/platform/esp/idf_common/src/bsp_runtime.cpp +++ b/platform/esp/idf_common/src/bsp_runtime.cpp @@ -4,14 +4,17 @@ #include "boards/t_display_p4/t_display_p4_board.h" #include "boards/tab5/tab5_board.h" +#include "driver/sdmmc_host.h" #include "esp_err.h" #include "esp_log.h" #include "nvs_flash.h" +#include "platform/esp/idf_common/sd_card_runtime_sdfat_adapter.h" +#include "platform/esp/idf_common/sdmmc_host_runtime.h" #if defined(TRAIL_MATE_ESP_BOARD_TAB5) +#include "sd_pwr_ctrl_by_on_chip_ldo.h" extern "C" { - esp_err_t bsp_sdcard_init(char* mount_point, size_t max_files); esp_err_t bsp_display_brightness_set(int brightness_percent); bool trail_mate_tab5_display_runtime_is_ready(void); } @@ -28,11 +31,14 @@ namespace { constexpr const char* kTag = "idf-bsp-runtime"; +constexpr int kTab5SdLdoChan = 4; char kSdMountPoint[] = "/sdcard"; bool s_nvs_ready = false; bool s_sdcard_ready = false; #if defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) bool s_sdcard_attempted = false; +#elif defined(TRAIL_MATE_ESP_BOARD_TAB5) +sd_pwr_ctrl_handle_t s_tab5_sd_pwr_ctrl_handle = nullptr; #endif } // namespace @@ -76,14 +82,47 @@ bool ensure_sdcard_ready() return false; } - esp_err_t err = bsp_sdcard_init(kSdMountPoint, 8); - if (err == ESP_OK || err == ESP_ERR_INVALID_STATE) + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.slot = SDMMC_HOST_SLOT_0; + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + + if (!s_tab5_sd_pwr_ctrl_handle) + { + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = kTab5SdLdoChan, + }; + const esp_err_t ldo_err = + sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &s_tab5_sd_pwr_ctrl_handle); + if (ldo_err != ESP_OK) + { + ESP_LOGW(kTag, "Tab5 SD LDO init failed: %s", esp_err_to_name(ldo_err)); + return false; + } + } + host.pwr_ctrl_handle = s_tab5_sd_pwr_ctrl_handle; + + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.width = 4; + const auto& pins = ::boards::tab5::Tab5Board::sdmmcPins(); + slot_config.clk = static_cast(pins.clk); + slot_config.cmd = static_cast(pins.cmd); + slot_config.d0 = static_cast(pins.d0); + slot_config.d1 = static_cast(pins.d1); + slot_config.d2 = static_cast(pins.d2); + slot_config.d3 = static_cast(pins.d3); + + if (::platform::esp::idf_common::sd_card_runtime::mount_sdmmc( + ::platform::esp::idf_common::sdmmc_host_runtime::SlotOwner::SdCard, + host, + slot_config, + kSdMountPoint, + 8)) { s_sdcard_ready = true; return true; } - ESP_LOGW(kTag, "bsp_sdcard_init failed: %s", esp_err_to_name(err)); + ESP_LOGW(kTag, "Tab5 SD unavailable via SdFat SDMMC backend"); return false; #elif defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) if (s_sdcard_ready) @@ -119,6 +158,14 @@ bool sdcard_ready() return s_sdcard_ready; } +void mark_sdcard_unmounted() +{ + s_sdcard_ready = false; +#if defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) + s_sdcard_attempted = false; +#endif +} + const char* sdcard_mount_point() { return kSdMountPoint; diff --git a/platform/esp/idf_common/src/chat_blob_store_io.cpp b/platform/esp/idf_common/src/chat_blob_store_io.cpp index 12b23512..70efd179 100644 --- a/platform/esp/idf_common/src/chat_blob_store_io.cpp +++ b/platform/esp/idf_common/src/chat_blob_store_io.cpp @@ -2,11 +2,11 @@ #if defined(ESP_PLATFORM) && !defined(ARDUINO) +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "platform/ui/settings_store.h" #include -#include #include namespace chat::infra @@ -21,29 +21,26 @@ bool valid_key(const char* value) return value && value[0] != '\0'; } -std::string absolute_sd_path(const char* path) +std::string logical_sd_path(const char* path) { if (!path || path[0] == '\0') { return {}; } - - const char* mount = ::platform::esp::idf_common::bsp_runtime::sdcard_mount_point(); - std::string absolute = mount ? mount : ""; - if (absolute.empty()) + std::string logical = path; + if (logical.size() >= 2 && (logical[0] == 'A' || logical[0] == 'a') && logical[1] == ':') { - return {}; + logical.erase(0, 2); } - if (absolute.back() == '/' && path[0] == '/') + if (logical.empty()) { - absolute.pop_back(); + return "/"; } - else if (absolute.back() != '/' && path[0] != '/') + if (logical.front() != '/') { - absolute.push_back('/'); + logical.insert(logical.begin(), '/'); } - absolute += path; - return absolute; + return logical; } } // namespace @@ -57,22 +54,16 @@ bool loadRawBlobFromSd(const char* path, std::vector& out, std::size_t return false; } - const std::string absolute = absolute_sd_path(path); - FILE* file = std::fopen(absolute.c_str(), "rb"); - if (!file) + const std::string logical = logical_sd_path(path); + ::platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(logical.c_str(), "rb")) { return false; } - if (std::fseek(file, 0, SEEK_END) != 0) + const uint64_t file_size = file.size(); + if (file_size == 0 || file_size > max_len || !file.seek(0)) { - std::fclose(file); - return false; - } - const long file_size = std::ftell(file); - if (file_size <= 0 || static_cast(file_size) > max_len || - std::fseek(file, 0, SEEK_SET) != 0) - { - std::fclose(file); + file.close(); return false; } @@ -83,17 +74,17 @@ bool loadRawBlobFromSd(const char* path, std::vector& out, std::size_t { const std::size_t chunk = std::min(sizeof(buffer), static_cast(file_size) - total); - const std::size_t read = std::fread(buffer, 1, chunk, file); - if (read != chunk) + const int read = file.read(buffer, chunk); + if (read < 0 || static_cast(read) != chunk) { - std::fclose(file); + file.close(); out.clear(); return false; } - out.insert(out.end(), buffer, buffer + read); - total += read; + out.insert(out.end(), buffer, buffer + static_cast(read)); + total += static_cast(read); } - std::fclose(file); + file.close(); return true; } @@ -105,27 +96,28 @@ bool saveRawBlobToSd(const char* path, const uint8_t* data, std::size_t len) return false; } - const std::string absolute = absolute_sd_path(path); - const std::string temporary = absolute + ".tmp"; - std::remove(temporary.c_str()); + const std::string logical = logical_sd_path(path); + const std::string temporary = logical + ".tmp"; + (void)::platform::esp::arduino_common::storage::sd_remove(temporary.c_str()); - FILE* file = std::fopen(temporary.c_str(), "wb"); - if (!file) + ::platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(temporary.c_str(), "wb")) { return false; } - const std::size_t written = len == 0 ? 0 : std::fwrite(data, 1, len, file); - const int close_status = std::fclose(file); - if (written != len || close_status != 0) + const std::size_t written = len == 0 ? 0 : file.write(data, len); + const bool flushed = file.flush(); + file.close(); + if (written != len || !flushed) { - std::remove(temporary.c_str()); + (void)::platform::esp::arduino_common::storage::sd_remove(temporary.c_str()); return false; } - std::remove(absolute.c_str()); - if (std::rename(temporary.c_str(), absolute.c_str()) != 0) + (void)::platform::esp::arduino_common::storage::sd_remove(logical.c_str()); + if (!::platform::esp::arduino_common::storage::sd_rename(temporary.c_str(), logical.c_str())) { - std::remove(temporary.c_str()); + (void)::platform::esp::arduino_common::storage::sd_remove(temporary.c_str()); return false; } return true; diff --git a/platform/esp/idf_common/src/debug/sd_coredump_export.cpp b/platform/esp/idf_common/src/debug/sd_coredump_export.cpp index 092e9ed5..85b8e1d5 100644 --- a/platform/esp/idf_common/src/debug/sd_coredump_export.cpp +++ b/platform/esp/idf_common/src/debug/sd_coredump_export.cpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include "esp_core_dump.h" #include "esp_err.h" @@ -14,6 +12,7 @@ #include "esp_random.h" #include "esp_system.h" #include "esp_timer.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "sdkconfig.h" @@ -55,15 +54,8 @@ void copy_path(char* out, std::size_t out_size, const char* value) bool path_is_dir(const char* path) { - if (!path || path[0] == '\0') - { - return false; - } - - struct stat st - { - }; - return stat(path, &st) == 0 && S_ISDIR(st.st_mode); + return path && path[0] != '\0' && + ::platform::esp::arduino_common::storage::sd_is_directory(path); } bool ensure_dir(const char* path) @@ -72,7 +64,7 @@ bool ensure_dir(const char* path) { return true; } - if (mkdir(path, 0755) == 0) + if (::platform::esp::arduino_common::storage::sd_mkdir(path)) { return true; } @@ -89,19 +81,13 @@ bool build_trailmate_path(char* out, return false; } - const char* mount = bsp_runtime::sdcard_mount_point(); - if (!mount || mount[0] == '\0') - { - return false; - } - if (second) { - std::snprintf(out, out_size, "%s/%s/%s", mount, first, second); + std::snprintf(out, out_size, "/%s/%s", first, second); } else { - std::snprintf(out, out_size, "%s/%s", mount, first); + std::snprintf(out, out_size, "/%s", first); } out[out_size - 1] = '\0'; return true; @@ -216,36 +202,33 @@ bool write_coredump_metadata(const char* coredump_path, std::snprintf(metadata_path, sizeof(metadata_path), "%s.txt", coredump_path); metadata_path[sizeof(metadata_path) - 1] = '\0'; - FILE* metadata = std::fopen(metadata_path, "w"); - if (!metadata) + ::platform::esp::arduino_common::storage::SdRuntimeFile metadata; + if (!metadata.open(metadata_path, "w")) { return false; } - std::fprintf(metadata, "path=%s\n", coredump_path); - std::fprintf(metadata, "size=%lu\n", static_cast(coredump_size)); - std::fprintf(metadata, "flash_addr=0x%08lX\n", static_cast(flash_addr)); - std::fprintf(metadata, "check_result=0x%08lX\n", static_cast(check_result)); - std::fprintf(metadata, "erase_result=0x%08lX\n", static_cast(erase_result)); - std::fprintf(metadata, "reset_reason=%s\n", reset_reason_name(esp_reset_reason())); + metadata.printf("path=%s\n", coredump_path); + metadata.printf("size=%lu\n", static_cast(coredump_size)); + metadata.printf("flash_addr=0x%08lX\n", static_cast(flash_addr)); + metadata.printf("check_result=0x%08lX\n", static_cast(check_result)); + metadata.printf("erase_result=0x%08lX\n", static_cast(erase_result)); + metadata.printf("reset_reason=%s\n", reset_reason_name(esp_reset_reason())); if (summary.available) { - std::fprintf(metadata, "exception_task=%s\n", summary.exception_task); - std::fprintf(metadata, - "exception_pc=0x%08lX\n", - static_cast(summary.exception_pc)); - std::fprintf(metadata, - "exception_tcb=0x%08lX\n", - static_cast(summary.exception_tcb)); - std::fprintf(metadata, - "core_dump_version=0x%08lX\n", - static_cast(summary.core_dump_version)); - std::fprintf(metadata, "app_elf_sha256=%s\n", summary.app_elf_sha256); + metadata.printf("exception_task=%s\n", summary.exception_task); + metadata.printf("exception_pc=0x%08lX\n", + static_cast(summary.exception_pc)); + metadata.printf("exception_tcb=0x%08lX\n", + static_cast(summary.exception_tcb)); + metadata.printf("core_dump_version=0x%08lX\n", + static_cast(summary.core_dump_version)); + metadata.printf("app_elf_sha256=%s\n", summary.app_elf_sha256); } - const bool ok = std::fflush(metadata) == 0; - std::fclose(metadata); + const bool ok = metadata.flush(); + metadata.close(); return ok; } @@ -259,8 +242,8 @@ bool export_coredump_payload(const esp_partition_t* partition, return false; } - FILE* out = std::fopen(path, "wb"); - if (!out) + ::platform::esp::arduino_common::storage::SdRuntimeFile out; + if (!out.open(path, "wb")) { return false; } @@ -272,24 +255,24 @@ bool export_coredump_payload(const esp_partition_t* partition, const std::size_t to_read = std::min(kCoredumpChunkBytes, size - offset); if (esp_partition_read(partition, partition_offset + offset, buffer, to_read) != ESP_OK) { - std::fclose(out); - std::remove(path); + out.close(); + ::platform::esp::arduino_common::storage::sd_remove(path); return false; } - if (std::fwrite(buffer, 1, to_read, out) != to_read) + if (out.write(buffer, to_read) != to_read) { - std::fclose(out); - std::remove(path); + out.close(); + ::platform::esp::arduino_common::storage::sd_remove(path); return false; } offset += to_read; } - const bool ok = std::fflush(out) == 0; - std::fclose(out); + const bool ok = out.flush(); + out.close(); if (!ok) { - std::remove(path); + ::platform::esp::arduino_common::storage::sd_remove(path); } return ok; } diff --git a/platform/esp/idf_common/src/platform_ui_pack_repository_runtime.cpp b/platform/esp/idf_common/src/platform_ui_pack_repository_runtime.cpp deleted file mode 100644 index 1f4e1c0f..00000000 --- a/platform/esp/idf_common/src/platform_ui_pack_repository_runtime.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "platform/ui/pack_repository_runtime.h" - -namespace ui::runtime::packs -{ - -bool is_supported() -{ - return false; -} - -bool load_installed_packages(std::vector& out_installed, std::string& out_error) -{ - out_installed.clear(); - out_error = "Pack installation is unsupported on this IDF target"; - return false; -} - -bool fetch_catalog(std::vector& out_packages, std::string& out_error) -{ - out_packages.clear(); - out_error = "Pack installation is unsupported on this IDF target"; - return false; -} - -bool install_package(const PackageRecord& package, std::string& out_error) -{ - (void)package; - out_error = "Pack installation is unsupported on this IDF target"; - return false; -} - -bool start_install_package(const PackageRecord& package, std::string& out_error) -{ - return install_package(package, out_error); -} - -PackageInstallStatus install_status() -{ - PackageInstallStatus status{}; - status.phase = PackageInstallPhase::Idle; - return status; -} - -bool uninstall_package(const PackageRecord& package, std::string& out_error) -{ - (void)package; - out_error = "Pack installation is unsupported on this IDF target"; - return false; -} - -} // namespace ui::runtime::packs diff --git a/platform/esp/idf_common/src/platform_ui_reticulum_directory_runtime.cpp b/platform/esp/idf_common/src/platform_ui_reticulum_directory_runtime.cpp index 05b77e76..2f4df816 100644 --- a/platform/esp/idf_common/src/platform_ui_reticulum_directory_runtime.cpp +++ b/platform/esp/idf_common/src/platform_ui_reticulum_directory_runtime.cpp @@ -2,6 +2,7 @@ #include "platform/ui/reticulum_page_runtime.h" #include "chat/ports/i_mesh_peer_directory.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "freertos/FreeRTOS.h" @@ -9,11 +10,9 @@ #include "freertos/task.h" #include -#include #include #include #include -#include #include namespace platform::ui::reticulum_directory @@ -120,22 +119,23 @@ bool sd_ready() std::string make_sd_path(const char* relative) { - const char* mount = - platform::esp::idf_common::bsp_runtime::sdcard_mount_point(); - std::string path = mount ? mount : ""; - if (path.empty() || !relative || !relative[0]) + if (!relative || !relative[0]) { - return path; + return "/"; } - if (path.back() == '/' && relative[0] == '/') + std::string path = relative; + if (path.size() >= 2 && (path[0] == 'A' || path[0] == 'a') && path[1] == ':') { - path.pop_back(); + path.erase(0, 2); } - else if (path.back() != '/' && relative[0] != '/') + if (path.empty()) { - path.push_back('/'); + return "/"; + } + if (path.front() != '/') + { + path.insert(path.begin(), '/'); } - path += relative; return path; } @@ -146,23 +146,15 @@ bool is_regular_file(const char* relative) return false; } const std::string path = make_sd_path(relative); - struct stat info = {}; - return ::stat(path.c_str(), &info) == 0 && S_ISREG(info.st_mode); + return ::platform::esp::arduino_common::storage::sd_exists(path.c_str()) && + !::platform::esp::arduino_common::storage::sd_is_directory(path.c_str()); } bool ensure_directory(const char* relative) { const std::string path = make_sd_path(relative); - struct stat info = {}; - if (::stat(path.c_str(), &info) == 0) - { - return S_ISDIR(info.st_mode); - } - if (::mkdir(path.c_str(), 0775) == 0) - { - return true; - } - return ::stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode); + return ::platform::esp::arduino_common::storage::sd_is_directory(path.c_str()) || + ::platform::esp::arduino_common::storage::sd_mkdir(path.c_str()); } bool ensure_reticulum_directory() @@ -181,33 +173,35 @@ AnnounceLoadResult load_persisted_announces( } const std::string path = make_sd_path(kAnnouncesPath); - FILE* file = std::fopen(path.c_str(), "rb"); - if (!file) + ::platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(path.c_str(), "rb")) { - return errno == ENOENT ? AnnounceLoadResult::Missing - : AnnounceLoadResult::IoError; + return ::platform::esp::arduino_common::storage::sd_exists(path.c_str()) + ? AnnounceLoadResult::IoError + : AnnounceLoadResult::Missing; } AnnounceFileHeader header{}; - if (std::fread(&header, 1, sizeof(header), file) != sizeof(header) || + if (file.read(&header, sizeof(header)) != static_cast(sizeof(header)) || header.magic != kAnnounceMagic || header.version != kAnnounceVersion || header.record_size != sizeof(PersistedAnnounce) || header.count > kMaxAnnounceRecords) { - std::fclose(file); + file.close(); return AnnounceLoadResult::IoError; } out.resize(header.count); const std::size_t payload_len = out.size() * sizeof(PersistedAnnounce); if ((payload_len != 0 && - std::fread(out.data(), 1, payload_len, file) != payload_len) || - std::fclose(file) != 0) + file.read(out.data(), payload_len) != static_cast(payload_len))) { + file.close(); out.clear(); return AnnounceLoadResult::IoError; } + file.close(); if (fnv1a32(out.data(), payload_len) != header.checksum) { out.clear(); @@ -226,9 +220,9 @@ bool save_persisted_announces(const std::vector& records) const std::string path = make_sd_path(kAnnouncesPath); const std::string temp_path = path + ".tmp"; - std::remove(temp_path.c_str()); - FILE* file = std::fopen(temp_path.c_str(), "wb"); - if (!file) + (void)::platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); + ::platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(temp_path.c_str(), "wb")) { return false; } @@ -237,22 +231,23 @@ bool save_persisted_announces(const std::vector& records) header.count = static_cast(records.size()); const std::size_t payload_len = records.size() * sizeof(PersistedAnnounce); header.checksum = fnv1a32(records.data(), payload_len); - bool ok = std::fwrite(&header, 1, sizeof(header), file) == sizeof(header); + bool ok = file.write(&header, sizeof(header)) == sizeof(header); if (ok && payload_len != 0) { - ok = std::fwrite(records.data(), 1, payload_len, file) == payload_len; + ok = file.write(records.data(), payload_len) == payload_len; } - ok = std::fclose(file) == 0 && ok; + ok = file.flush() && ok; + file.close(); if (!ok) { - std::remove(temp_path.c_str()); + (void)::platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); return false; } - std::remove(path.c_str()); - if (std::rename(temp_path.c_str(), path.c_str()) != 0) + (void)::platform::esp::arduino_common::storage::sd_remove(path.c_str()); + if (!::platform::esp::arduino_common::storage::sd_rename(temp_path.c_str(), path.c_str())) { - std::remove(temp_path.c_str()); + (void)::platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); return false; } return true; diff --git a/platform/esp/idf_common/src/platform_ui_reticulum_group_config_runtime.cpp b/platform/esp/idf_common/src/platform_ui_reticulum_group_config_runtime.cpp index 0a7201f0..73c011ef 100644 --- a/platform/esp/idf_common/src/platform_ui_reticulum_group_config_runtime.cpp +++ b/platform/esp/idf_common/src/platform_ui_reticulum_group_config_runtime.cpp @@ -1,11 +1,12 @@ #include "platform/ui/reticulum_group_config_runtime.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include #include #include -#include +#include namespace platform::ui::reticulum_groups { @@ -32,47 +33,45 @@ void set_status(Status& out, const char* message, const char* detail = nullptr) copy_text(out.detail, sizeof(out.detail), detail); } -std::string mount_path_for(const char* logical_path) +std::string logical_path_for(const char* logical_path) { - return std::string(::platform::esp::idf_common::bsp_runtime::sdcard_mount_point()) + - (logical_path ? logical_path : ""); + if (!logical_path || logical_path[0] == '\0') + { + return "/"; + } + std::string path = logical_path; + if (path.size() >= 2 && (path[0] == 'A' || path[0] == 'a') && path[1] == ':') + { + path.erase(0, 2); + } + if (path.empty()) + { + return "/"; + } + if (path.front() != '/') + { + path.insert(path.begin(), '/'); + } + return path; } bool path_exists(const char* logical_path) { - struct stat st - { - }; - return ::stat(mount_path_for(logical_path).c_str(), &st) == 0; + const std::string path = logical_path_for(logical_path); + return ::platform::esp::arduino_common::storage::sd_exists(path.c_str()); } bool is_directory(const char* logical_path) { - struct stat st - { - }; - return ::stat(mount_path_for(logical_path).c_str(), &st) == 0 && S_ISDIR(st.st_mode); + const std::string path = logical_path_for(logical_path); + return ::platform::esp::arduino_common::storage::sd_is_directory(path.c_str()); } bool ensure_config_dir() { - const std::string root = mount_path_for("/trailmate"); - const std::string reticulum = mount_path_for(kConfigDir); - struct stat st - { - }; - if (::stat(root.c_str(), &st) != 0) - { - if (::mkdir(root.c_str(), 0775) != 0) - { - return false; - } - } - if (::stat(reticulum.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) - { - return true; - } - return ::mkdir(reticulum.c_str(), 0775) == 0 || is_directory(kConfigDir); + return ::platform::esp::arduino_common::storage::sd_mkdir("/trailmate") && + (::platform::esp::arduino_common::storage::sd_mkdir(kConfigDir) || + is_directory(kConfigDir)); } bool enabled_text(const std::string& value) @@ -131,57 +130,54 @@ bool parse_group_line(const std::string& line, bool read_config_text(std::string& out) { out.clear(); - const std::string path = mount_path_for(kConfigPath); - FILE* file = std::fopen(path.c_str(), "rb"); - if (!file) + const std::string path = logical_path_for(kConfigPath); + ::platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(path.c_str(), "rb")) { return false; } - if (std::fseek(file, 0, SEEK_END) != 0) + const uint64_t size = file.size(); + if (size == 0 || size > kMaxConfigBytes || !file.seek(0)) { - std::fclose(file); + file.close(); return false; } - const long size = std::ftell(file); - if (size <= 0 || static_cast(size) > kMaxConfigBytes) - { - std::fclose(file); - return false; - } - std::rewind(file); - out.resize(static_cast(size)); - const std::size_t read = std::fread(&out[0], 1, out.size(), file); - std::fclose(file); - if (read != out.size()) + std::vector buffer(static_cast(size)); + const int read = file.read(buffer.data(), buffer.size()); + file.close(); + if (read < 0 || static_cast(read) != buffer.size()) { out.clear(); return false; } + out.assign(buffer.begin(), buffer.end()); return true; } bool write_text_atomic(const std::string& text) { - const std::string temp_path = mount_path_for(kConfigTempPath); - const std::string final_path = mount_path_for(kConfigPath); - std::remove(temp_path.c_str()); + const std::string temp_path = logical_path_for(kConfigTempPath); + const std::string final_path = logical_path_for(kConfigPath); + (void)::platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); - FILE* file = std::fopen(temp_path.c_str(), "wb"); - if (!file) + ::platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(temp_path.c_str(), "wb")) { return false; } - const bool wrote = std::fwrite(text.data(), 1, text.size(), file) == text.size(); - std::fclose(file); - if (!wrote) + const bool wrote = file.write(text.data(), text.size()) == text.size(); + const bool flushed = file.flush(); + file.close(); + if (!wrote || !flushed) { - std::remove(temp_path.c_str()); + (void)::platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); return false; } - std::remove(final_path.c_str()); - if (std::rename(temp_path.c_str(), final_path.c_str()) != 0) + (void)::platform::esp::arduino_common::storage::sd_remove(final_path.c_str()); + if (!::platform::esp::arduino_common::storage::sd_rename(temp_path.c_str(), + final_path.c_str())) { - std::remove(temp_path.c_str()); + (void)::platform::esp::arduino_common::storage::sd_remove(temp_path.c_str()); return false; } return true; diff --git a/platform/esp/idf_common/src/platform_ui_route_storage.cpp b/platform/esp/idf_common/src/platform_ui_route_storage.cpp deleted file mode 100644 index 8b45a92d..00000000 --- a/platform/esp/idf_common/src/platform_ui_route_storage.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include "platform/ui/route_storage.h" - -#include "platform/esp/idf_common/bsp_runtime.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace platform::ui::route_storage -{ -namespace -{ - -constexpr const char* kRouteDir = "/routes"; -constexpr const char* kRouteAssetRoot = "/routes/.trailmate"; -constexpr const char* kRouteAssetImageSubdir = "images"; - -bool has_kml_extension(const std::string& name) -{ - if (name.size() < 4) - { - return false; - } - std::string lower = name; - std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char ch) - { return static_cast(std::tolower(ch)); }); - return lower.compare(lower.size() - 4, 4, ".kml") == 0; -} - -bool is_safe_asset_id(const std::string& asset_id) -{ - if (asset_id.empty() || asset_id.size() > 48) - { - return false; - } - for (unsigned char ch : asset_id) - { - if (std::isalnum(ch) || ch == '-' || ch == '_' || ch == '.') - { - continue; - } - return false; - } - return true; -} - -bool parse_route_image_name(const char* name, - std::size_t max_count, - std::size_t& out_index) -{ - out_index = 0; - if (name == nullptr || max_count == 0) - { - return false; - } - if (std::strlen(name) != 12 || std::strncmp(name, "img-", 4) != 0) - { - return false; - } - std::uint32_t value = 0; - for (int offset = 4; offset < 8; ++offset) - { - const char ch = name[offset]; - if (ch < '0' || ch > '9') - { - return false; - } - value = (value * 10U) + static_cast(ch - '0'); - } - if (name[8] != '.' || - std::tolower(static_cast(name[9])) != 'j' || - std::tolower(static_cast(name[10])) != 'p' || - std::tolower(static_cast(name[11])) != 'g' || - name[12] != '\0' || - value == 0 || - value > max_count) - { - return false; - } - out_index = static_cast(value - 1U); - return true; -} - -bool is_regular_file(const std::string& path) -{ - struct stat st - { - }; - return ::stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode); -} - -bool is_directory(const std::string& path) -{ - struct stat st - { - }; - return ::stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode); -} - -std::string mount_path_for(const std::string& logical_path) -{ - return std::string(platform::esp::idf_common::bsp_runtime::sdcard_mount_point()) + logical_path; -} - -bool ensure_dir(const std::string& logical_path) -{ - const std::string path = mount_path_for(logical_path); - return is_directory(path) || ::mkdir(path.c_str(), 0775) == 0 || is_directory(path); -} - -} // namespace - -bool is_supported() -{ - return platform::esp::idf_common::bsp_runtime::sdcard_capable(); -} - -bool list_routes(std::vector& out_routes, std::size_t max_count) -{ - out_routes.clear(); - if (!platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready()) - { - return false; - } - - const std::string base = std::string(platform::esp::idf_common::bsp_runtime::sdcard_mount_point()) + kRouteDir; - DIR* dir = ::opendir(base.c_str()); - if (dir == nullptr) - { - return false; - } - - while (out_routes.size() < max_count) - { - dirent* entry = ::readdir(dir); - if (entry == nullptr) - { - break; - } - const char* name_c = entry->d_name; - if (name_c == nullptr || ::strcmp(name_c, ".") == 0 || ::strcmp(name_c, "..") == 0) - { - continue; - } - std::string name = name_c; - if (!has_kml_extension(name)) - { - continue; - } - const std::string full_path = base + "/" + name; - if (is_regular_file(full_path)) - { - out_routes.push_back(name); - } - } - ::closedir(dir); - std::sort(out_routes.begin(), out_routes.end()); - return true; -} - -bool remove_route(const std::string& path) -{ - if (!platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() || path.empty()) - { - return false; - } - const std::string mount_prefixed = std::string(platform::esp::idf_common::bsp_runtime::sdcard_mount_point()) + path; - return std::remove(mount_prefixed.c_str()) == 0; -} - -const char* route_dir() -{ - return kRouteDir; -} - -bool ensure_route_asset_dir(const std::string& asset_id, std::string& out_dir) -{ - out_dir.clear(); - if (!platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() || - !is_safe_asset_id(asset_id)) - { - return false; - } - if (!ensure_dir(kRouteDir) || !ensure_dir(kRouteAssetRoot)) - { - return false; - } - out_dir = std::string(kRouteAssetRoot) + "/" + asset_id; - if (!ensure_dir(out_dir) || - !ensure_dir(out_dir + "/images") || - !ensure_dir(out_dir + "/thumbs") || - !ensure_dir(out_dir + "/views")) - { - out_dir.clear(); - return false; - } - return true; -} - -bool count_route_saved_images(const std::string& asset_id, - std::size_t max_count, - std::size_t& out_count) -{ - out_count = 0; - if (!platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() || - !is_safe_asset_id(asset_id) || - max_count == 0) - { - return false; - } - - const std::string image_dir = - mount_path_for(std::string(kRouteAssetRoot) + "/" + asset_id + "/" + kRouteAssetImageSubdir); - DIR* dir = ::opendir(image_dir.c_str()); - if (dir == nullptr) - { - return false; - } - - std::vector seen(max_count, 0); - while (out_count < max_count) - { - dirent* entry = ::readdir(dir); - if (entry == nullptr) - { - break; - } - std::size_t index = 0; - if (parse_route_image_name(entry->d_name, max_count, index) && - index < seen.size() && - !seen[index]) - { - const std::string full_path = image_dir + "/" + entry->d_name; - if (is_regular_file(full_path)) - { - seen[index] = 1; - ++out_count; - } - } - } - ::closedir(dir); - return true; -} - -bool route_asset_file_exists(const std::string& path) -{ - if (!platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready() || path.empty()) - { - return false; - } - return is_regular_file(mount_path_for(path)); -} - -RouteImageDownloadResult download_route_image(const std::string& url, - const std::string& output_path, - std::uint32_t max_bytes) -{ - (void)url; - (void)output_path; - (void)max_bytes; - RouteImageDownloadResult result{}; - result.error = "Route image download unsupported on this target"; - return result; -} - -bool start_route_image_download(const std::string& asset_id, - std::vector items, - std::string& out_error, - RouteImageTaskPresentation presentation) -{ - (void)asset_id; - (void)items; - (void)presentation; - out_error = "Route image download unsupported on this target"; - return false; -} - -bool start_route_image_cache_build(const std::string& asset_id, - const std::vector& items, - std::string& out_error, - RouteImageTaskPresentation presentation) -{ - (void)asset_id; - (void)items; - (void)presentation; - out_error = "Route image cache unsupported on this target"; - return false; -} - -RouteImageDownloadStatus route_image_download_status() -{ - RouteImageDownloadStatus status{}; - return status; -} - -} // namespace platform::ui::route_storage diff --git a/platform/esp/idf_common/src/platform_ui_settings_backup_runtime.cpp b/platform/esp/idf_common/src/platform_ui_settings_backup_runtime.cpp deleted file mode 100644 index 105fbdb2..00000000 --- a/platform/esp/idf_common/src/platform_ui_settings_backup_runtime.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "platform/ui/settings_backup_runtime.h" - -#include - -namespace platform::ui::settings_backup -{ - -bool is_supported() -{ - return false; -} - -Status status() -{ - Status out{}; - out.supported = false; - std::snprintf(out.message, sizeof(out.message), "%s", "Settings backup unsupported"); - return out; -} - -bool backup() -{ - return false; -} - -bool restore() -{ - return false; -} - -bool remove() -{ - return false; -} - -} // namespace platform::ui::settings_backup diff --git a/platform/esp/idf_common/src/platform_ui_team_ui_store_runtime.cpp b/platform/esp/idf_common/src/platform_ui_team_ui_store_runtime.cpp deleted file mode 100644 index 317de797..00000000 --- a/platform/esp/idf_common/src/platform_ui_team_ui_store_runtime.cpp +++ /dev/null @@ -1,317 +0,0 @@ -#include "platform/ui/team_ui_store_runtime.h" - -#include -#include -#include -#include - -namespace team::ui -{ -namespace -{ - -uint64_t team_id_to_u64(const TeamId& id) -{ - uint64_t value = 0; - for (size_t i = 0; i < id.size(); ++i) - { - value |= (static_cast(id[i]) << (8U * i)); - } - return value; -} - -struct TeamUiRuntimeMemory -{ - std::map> chat_logs{}; - std::map> latest_positions{}; -}; - -TeamUiRuntimeMemory& runtime_memory() -{ - static TeamUiRuntimeMemory memory{}; - return memory; -} - -constexpr size_t kMaxChatEntriesPerTeam = 64; -constexpr size_t kMaxPosSamplesPerTeam = 64; - -TeamUiSnapshotMemoryStore s_snapshot_memory_store{}; -ITeamUiSnapshotStore* s_snapshot_store = &s_snapshot_memory_store; - -class TeamUiRuntimeChatLogStore final : public ITeamUiChatLogStore -{ - public: - bool appendText(const TeamId& team_id, - uint32_t peer_id, - bool incoming, - uint32_t ts, - const std::string& text) override - { - return appendStructured(team_id, - peer_id, - incoming, - ts, - team::proto::TeamChatType::Text, - std::vector(text.begin(), text.end())); - } - - bool appendStructured(const TeamId& team_id, - uint32_t peer_id, - bool incoming, - uint32_t ts, - team::proto::TeamChatType type, - const std::vector& payload) override - { - auto& chat_log = runtime_memory().chat_logs[team_id_to_u64(team_id)]; - TeamChatLogEntry entry; - entry.incoming = incoming; - entry.ts = ts; - entry.peer_id = peer_id; - entry.type = type; - entry.payload = payload; - chat_log.push_back(std::move(entry)); - while (chat_log.size() > kMaxChatEntriesPerTeam) - { - chat_log.pop_front(); - } - return true; - } - - bool loadRecent(const TeamId& team_id, - std::size_t max_count, - std::vector& out) override - { - out.clear(); - const auto it = runtime_memory().chat_logs.find(team_id_to_u64(team_id)); - if (it == runtime_memory().chat_logs.end()) - { - return false; - } - - const auto& log = it->second; - const size_t count = std::min(max_count, log.size()); - auto start = log.end(); - std::advance(start, -static_cast(count)); - out.assign(start, log.end()); - return !out.empty(); - } -}; - -TeamUiRuntimeChatLogStore s_chat_log_store{}; - -} // namespace - -bool TeamUiSnapshotMemoryStore::has_snapshot_ = false; -TeamUiSnapshot TeamUiSnapshotMemoryStore::snapshot_{}; - -bool TeamUiSnapshotMemoryStore::load(TeamUiSnapshot& out) -{ - if (!has_snapshot_) - { - return false; - } - - out = snapshot_; - return true; -} - -void TeamUiSnapshotMemoryStore::save(const TeamUiSnapshot& in) -{ - snapshot_ = in; - has_snapshot_ = true; -} - -void TeamUiSnapshotMemoryStore::clear() -{ - snapshot_ = TeamUiSnapshot{}; - has_snapshot_ = false; - runtime_memory() = TeamUiRuntimeMemory{}; -} - -ITeamUiSnapshotStore& team_ui_snapshot_store() -{ - return *s_snapshot_store; -} - -void team_ui_set_snapshot_store(ITeamUiSnapshotStore* store) -{ - s_snapshot_store = store ? store : &s_snapshot_memory_store; -} - -ITeamUiStore& team_ui_get_store() -{ - return team_ui_snapshot_store(); -} - -void team_ui_set_store(ITeamUiStore* store) -{ - team_ui_set_snapshot_store(store); -} - -ITeamUiChatLogStore& team_ui_chat_log_store() -{ - return s_chat_log_store; -} - -bool team_ui_append_key_event(const TeamId& team_id, - TeamKeyEventType type, - uint32_t event_seq, - uint32_t ts, - const uint8_t* payload, - size_t len) -{ - (void)ts; - (void)payload; - (void)len; - - TeamUiSnapshot snapshot{}; - if (!team_ui_snapshot_store().load(snapshot)) - { - snapshot = TeamUiSnapshot{}; - } - - snapshot.team_id = team_id; - snapshot.has_team_id = true; - snapshot.last_event_seq = event_seq; - - if (type == TeamKeyEventType::TeamCreated) - { - snapshot.in_team = true; - snapshot.self_is_leader = true; - snapshot.kicked_out = false; - } - - team_ui_snapshot_store().save(snapshot); - return true; -} - -bool team_ui_posring_append(const TeamId& team_id, - uint32_t member_id, - int32_t lat_e7, - int32_t lon_e7, - int16_t alt_m, - uint16_t speed_dmps, - uint32_t ts) -{ - auto& positions = runtime_memory().latest_positions[team_id_to_u64(team_id)]; - TeamPosSample sample; - sample.member_id = member_id; - sample.lat_e7 = lat_e7; - sample.lon_e7 = lon_e7; - sample.alt_m = alt_m; - sample.speed_dmps = speed_dmps; - sample.ts = ts; - positions.push_back(sample); - while (positions.size() > kMaxPosSamplesPerTeam) - { - positions.pop_front(); - } - return true; -} - -bool team_ui_posring_load_latest(const TeamId& team_id, std::vector& out) -{ - out.clear(); - const auto it = runtime_memory().latest_positions.find(team_id_to_u64(team_id)); - if (it == runtime_memory().latest_positions.end()) - { - return false; - } - - out.assign(it->second.begin(), it->second.end()); - return !out.empty(); -} - -bool team_ui_chatlog_append(const TeamId& team_id, - uint32_t peer_id, - bool incoming, - uint32_t ts, - const std::string& text) -{ - return team_ui_chat_log_store().appendText(team_id, - peer_id, - incoming, - ts, - text); -} - -bool team_ui_chatlog_append_structured(const TeamId& team_id, - uint32_t peer_id, - bool incoming, - uint32_t ts, - team::proto::TeamChatType type, - const std::vector& payload) -{ - return team_ui_chat_log_store().appendStructured(team_id, - peer_id, - incoming, - ts, - type, - payload); -} - -bool team_ui_chatlog_load_recent(const TeamId& team_id, - size_t max_count, - std::vector& out) -{ - return team_ui_chat_log_store().loadRecent(team_id, max_count, out); -} - -bool team_ui_save_keys_now(const TeamId& team_id, - uint32_t key_id, - const std::array& psk) -{ - TeamUiSnapshot snapshot{}; - if (!team_ui_snapshot_store().load(snapshot)) - { - snapshot = TeamUiSnapshot{}; - } - snapshot.team_id = team_id; - snapshot.has_team_id = true; - snapshot.security_round = key_id; - snapshot.team_psk = psk; - snapshot.has_team_psk = true; - team_ui_snapshot_store().save(snapshot); - return true; -} - -bool team_ui_append_member_track(const TeamId& team_id, - uint32_t member_id, - const team::proto::TeamTrackMessage& track) -{ - if (track.points.empty() || track.valid_mask == 0) - { - return false; - } - - bool appended = false; - for (size_t i = 0; i < track.points.size(); ++i) - { - if ((track.valid_mask & (1u << static_cast(i))) == 0) - { - continue; - } - const auto& point = track.points[i]; - const uint32_t ts = - track.start_ts + static_cast(track.interval_s) * static_cast(i); - appended = team_ui_posring_append(team_id, - member_id, - point.lat_e7, - point.lon_e7, - 0, - 0, - ts) || - appended; - } - return appended; -} - -bool team_ui_get_member_track_path(const TeamId& team_id, uint32_t member_id, std::string& out_path) -{ - (void)team_id; - (void)member_id; - out_path.clear(); - return false; -} - -} // namespace team::ui diff --git a/platform/esp/idf_common/src/platform_ui_tracker_runtime.cpp b/platform/esp/idf_common/src/platform_ui_tracker_runtime.cpp index af4a36ae..b5fe009a 100644 --- a/platform/esp/idf_common/src/platform_ui_tracker_runtime.cpp +++ b/platform/esp/idf_common/src/platform_ui_tracker_runtime.cpp @@ -1,6 +1,7 @@ #include "platform/ui/tracker_runtime.h" #include "esp_timer.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "platform/ui/gps_runtime.h" @@ -9,9 +10,7 @@ #include #include #include -#include #include -#include namespace platform::ui::tracker { @@ -33,7 +32,7 @@ struct TrackerRuntimeState bool has_last_point = false; double last_lat = 0.0; double last_lng = 0.0; - FILE* file = nullptr; + ::platform::esp::arduino_common::storage::SdRuntimeFile file; std::string current_rel_path; }; @@ -45,23 +44,28 @@ TrackerRuntimeState& state() bool is_regular_file(const std::string& path) { - struct stat st - { - }; - return ::stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode); + return ::platform::esp::arduino_common::storage::sd_exists(path.c_str()) && + !::platform::esp::arduino_common::storage::sd_is_directory(path.c_str()); } std::string full_path_for(const std::string& relative) { - const char* mount = platform::esp::idf_common::bsp_runtime::sdcard_mount_point(); - std::string path = mount ? mount : ""; - if (!relative.empty()) + if (relative.empty()) { - if (!path.empty() && path.back() == '/' && relative.front() == '/') - { - path.pop_back(); - } - path += relative; + return "/"; + } + std::string path = relative; + if (path.size() >= 2 && (path[0] == 'A' || path[0] == 'a') && path[1] == ':') + { + path.erase(0, 2); + } + if (path.empty()) + { + return "/"; + } + if (path.front() != '/') + { + path.insert(path.begin(), '/'); } return path; } @@ -73,14 +77,8 @@ bool ensure_track_dir() return false; } const std::string base = full_path_for(kTrackDir); - if (::mkdir(base.c_str(), 0775) == 0) - { - return true; - } - struct stat st - { - }; - return ::stat(base.c_str(), &st) == 0 && S_ISDIR(st.st_mode); + return ::platform::esp::arduino_common::storage::sd_mkdir(base.c_str()) || + ::platform::esp::arduino_common::storage::sd_is_directory(base.c_str()); } uint64_t now_ms() @@ -177,50 +175,49 @@ void format_iso_time(uint32_t seconds, char* out, size_t out_len) void write_header(TrackerRuntimeState& runtime) { - if (runtime.file == nullptr) + if (!runtime.file.is_open()) { return; } switch (runtime.format) { case Format::CSV: - std::fprintf(runtime.file, "time,lat,lon,alt_m,speed_mps,satellites\n"); + runtime.file.print("time,lat,lon,alt_m,speed_mps,satellites\n"); break; case Format::Binary: { static constexpr char kHeader[] = "TMTRK1"; - (void)std::fwrite(kHeader, 1, sizeof(kHeader) - 1, runtime.file); + (void)runtime.file.write(kHeader, sizeof(kHeader) - 1); break; } case Format::GPX: default: - std::fprintf(runtime.file, - "\n" - "\n" - " Trail Mate Track\n"); + runtime.file.print("\n" + "\n" + " Trail Mate Track\n"); break; } - std::fflush(runtime.file); + runtime.file.flush(); } void write_footer(TrackerRuntimeState& runtime) { - if (runtime.file == nullptr) + if (!runtime.file.is_open()) { return; } if (runtime.format == Format::GPX) { - std::fprintf(runtime.file, " \n\n"); + runtime.file.print(" \n\n"); } - std::fflush(runtime.file); + runtime.file.flush(); } void write_point(TrackerRuntimeState& runtime, const platform::ui::gps::GpsState& gps, uint32_t seconds) { - if (runtime.file == nullptr) + if (!runtime.file.is_open()) { return; } @@ -231,14 +228,13 @@ void write_point(TrackerRuntimeState& runtime, { char iso[24] = {}; format_iso_time(seconds, iso, sizeof(iso)); - std::fprintf(runtime.file, - "%s,%.7f,%.7f,%.2f,%.2f,%u\n", - iso[0] != '\0' ? iso : "", - gps.lat, - gps.lng, - gps.has_alt ? gps.alt_m : 0.0, - gps.has_speed ? gps.speed_mps : 0.0, - static_cast(gps.satellites)); + runtime.file.printf("%s,%.7f,%.7f,%.2f,%.2f,%u\n", + iso[0] != '\0' ? iso : "", + gps.lat, + gps.lng, + gps.has_alt ? gps.alt_m : 0.0, + gps.has_speed ? gps.speed_mps : 0.0, + static_cast(gps.satellites)); break; } case Format::Binary: @@ -262,7 +258,7 @@ void write_point(TrackerRuntimeState& runtime, point.satellites = gps.satellites; point.flags = static_cast((gps.has_alt ? 0x01 : 0x00) | (gps.has_speed ? 0x02 : 0x00)); - (void)std::fwrite(&point, sizeof(point), 1, runtime.file); + (void)runtime.file.write(&point, sizeof(point)); break; } case Format::GPX: @@ -270,23 +266,22 @@ void write_point(TrackerRuntimeState& runtime, { char iso[24] = {}; format_iso_time(seconds, iso, sizeof(iso)); - std::fprintf(runtime.file, - " ", - gps.lat, - gps.lng); + runtime.file.printf(" ", + gps.lat, + gps.lng); if (gps.has_alt) { - std::fprintf(runtime.file, "%.2f", gps.alt_m); + runtime.file.printf("%.2f", gps.alt_m); } if (iso[0] != '\0') { - std::fprintf(runtime.file, "", iso); + runtime.file.printf("", iso); } - std::fprintf(runtime.file, "\n"); + runtime.file.print("\n"); break; } } - std::fflush(runtime.file); + runtime.file.flush(); } } // namespace @@ -315,8 +310,7 @@ bool start_recording() runtime.current_rel_path = make_track_path(runtime.format); const std::string full_path = full_path_for(runtime.current_rel_path); - runtime.file = std::fopen(full_path.c_str(), "wb"); - if (runtime.file == nullptr) + if (!runtime.file.open(full_path.c_str(), "wb")) { runtime.current_rel_path.clear(); return false; @@ -338,11 +332,7 @@ void stop_recording() return; } write_footer(runtime); - if (runtime.file != nullptr) - { - std::fclose(runtime.file); - runtime.file = nullptr; - } + runtime.file.close(); runtime.recording = false; runtime.last_sample_ms = 0; runtime.has_last_point = false; @@ -359,7 +349,7 @@ void poll() } return; } - if (runtime.file == nullptr) + if (!runtime.file.is_open()) { stop_recording(); return; @@ -406,26 +396,26 @@ bool list_tracks(std::vector& out_tracks, std::size_t max_count) return false; } - const std::string base = std::string(platform::esp::idf_common::bsp_runtime::sdcard_mount_point()) + track_dir(); - DIR* dir = ::opendir(base.c_str()); - if (dir == nullptr) + const std::string base = full_path_for(track_dir()); + ::platform::esp::arduino_common::storage::SdRuntimeDir dir; + if (!dir.open(base.c_str())) { return false; } while (out_tracks.size() < max_count) { - dirent* entry = ::readdir(dir); - if (entry == nullptr) + char name_buffer[96] = {}; + bool is_dir = false; + if (!dir.read_next(name_buffer, sizeof(name_buffer), &is_dir)) { break; } - const char* name_c = entry->d_name; - if (name_c == nullptr || std::strcmp(name_c, ".") == 0 || std::strcmp(name_c, "..") == 0) + if (is_dir) { continue; } - std::string name = name_c; + std::string name = name_buffer; if (name == "active.bin") { continue; @@ -436,7 +426,7 @@ bool list_tracks(std::vector& out_tracks, std::size_t max_count) out_tracks.push_back(name); } } - ::closedir(dir); + dir.close(); std::sort(out_tracks.begin(), out_tracks.end()); return !out_tracks.empty(); } @@ -447,8 +437,8 @@ bool remove_track(const std::string& path) { return false; } - const std::string mount_prefixed = std::string(platform::esp::idf_common::bsp_runtime::sdcard_mount_point()) + path; - return std::remove(mount_prefixed.c_str()) == 0; + const std::string logical_path = full_path_for(path); + return ::platform::esp::arduino_common::storage::sd_remove(logical_path.c_str()); } const char* track_dir() diff --git a/platform/esp/idf_common/src/platform_ui_usb_support_runtime.cpp b/platform/esp/idf_common/src/platform_ui_usb_support_runtime.cpp index 0070c815..54cf61c8 100644 --- a/platform/esp/idf_common/src/platform_ui_usb_support_runtime.cpp +++ b/platform/esp/idf_common/src/platform_ui_usb_support_runtime.cpp @@ -15,7 +15,6 @@ #include "app/app_facade_access.h" #if defined(TRAIL_MATE_ESP_BOARD_TAB5) #include "boards/tab5/tab5_board.h" -#include "sd_pwr_ctrl_by_on_chip_ldo.h" #elif defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) #include "boards/t_display_p4/t_display_p4_board.h" #include "platform/esp/idf_common/usb_console_runtime.h" @@ -24,6 +23,7 @@ #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "platform/esp/idf_common/sdmmc_host_runtime.h" #include "platform/ui/gps_runtime.h" @@ -32,10 +32,6 @@ #include "team/usecase/team_pairing_service.h" #include "tinyusb.h" #include "tusb_msc_storage.h" -#if defined(TRAIL_MATE_ESP_BOARD_TAB5) -extern "C" esp_err_t bsp_sdcard_init(char* mount_point, size_t max_files); -extern "C" esp_err_t bsp_sdcard_deinit(char* mount_point); -#endif #endif namespace platform::ui::usb_support @@ -68,7 +64,6 @@ void stop_pairing() constexpr const char* kUsbVendor = "TrailMate"; constexpr const char* kUsbProduct = "USB Disk"; constexpr const char* kUsbSerial = "TM-IDF"; -constexpr int kTab5SdLdoChan = 4; constexpr uint8_t kInterfaceMsc = 0; constexpr uint8_t kInterfaceTotal = 1; constexpr uint8_t kEndpointMscOut = 0x01; @@ -79,9 +74,6 @@ bool s_usb_installed = false; bool s_storage_initialized = false; sdmmc_card_t* s_card = nullptr; sdmmc_host_t s_sd_host = SDMMC_HOST_DEFAULT(); -#if defined(TRAIL_MATE_ESP_BOARD_TAB5) -sd_pwr_ctrl_handle_t s_pwr_ctrl_handle = nullptr; -#endif static tusb_desc_device_t s_device_descriptor = { .bLength = sizeof(s_device_descriptor), @@ -135,10 +127,16 @@ static uint8_t const s_msc_hs_configuration_desc[] = { bool unmount_application_sd() { #if defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) - return ::boards::t_display_p4::TDisplayP4Board::instance().unmountSdCard(); + const bool ok = ::boards::t_display_p4::TDisplayP4Board::instance().unmountSdCard(); + if (ok) + { + platform::esp::idf_common::bsp_runtime::mark_sdcard_unmounted(); + } + return ok; #else - return bsp_sdcard_deinit(const_cast( - platform::esp::idf_common::bsp_runtime::sdcard_mount_point())) == ESP_OK; + ::platform::esp::arduino_common::storage::unmount_sd_card(); + platform::esp::idf_common::bsp_runtime::mark_sdcard_unmounted(); + return true; #endif } @@ -148,9 +146,7 @@ bool remount_application_sd() return ::boards::t_display_p4::TDisplayP4Board::instance().mountSdCard( platform::esp::idf_common::bsp_runtime::sdcard_mount_point(), 8); #else - return bsp_sdcard_init(const_cast( - platform::esp::idf_common::bsp_runtime::sdcard_mount_point()), - 8) == ESP_OK; + return platform::esp::idf_common::bsp_runtime::ensure_sdcard_ready(); #endif } @@ -198,13 +194,6 @@ void deinit_sd_host() free(s_card); s_card = nullptr; } -#if defined(TRAIL_MATE_ESP_BOARD_TAB5) - if (s_pwr_ctrl_handle) - { - (void)sd_pwr_ctrl_del_on_chip_ldo(s_pwr_ctrl_handle); - s_pwr_ctrl_handle = nullptr; - } -#endif } esp_err_t init_sd_host_raw() @@ -218,21 +207,7 @@ esp_err_t init_sd_host_raw() s_sd_host.slot = SDMMC_HOST_SLOT_0; s_sd_host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; -#if defined(TRAIL_MATE_ESP_BOARD_TAB5) - sd_pwr_ctrl_ldo_config_t ldo_config = { - .ldo_chan_id = kTab5SdLdoChan, - }; - - if (!s_pwr_ctrl_handle) - { - const esp_err_t ldo_err = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &s_pwr_ctrl_handle); - if (ldo_err != ESP_OK) - { - return ldo_err; - } - } - s_sd_host.pwr_ctrl_handle = s_pwr_ctrl_handle; -#elif defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) +#if defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) if (!::boards::t_display_p4::TDisplayP4Board::instance().ensureExternal3v3Power()) { return ESP_FAIL; diff --git a/platform/esp/idf_common/src/sd_card_runtime_ffat_adapter.cpp b/platform/esp/idf_common/src/sd_card_runtime_ffat_adapter.cpp deleted file mode 100644 index df68ce74..00000000 --- a/platform/esp/idf_common/src/sd_card_runtime_ffat_adapter.cpp +++ /dev/null @@ -1,534 +0,0 @@ -#include "platform/esp/arduino_common/storage/sd_card_runtime.h" - -#include "platform/esp/idf_common/flash_storage_runtime.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace platform::esp::arduino_common::storage -{ -namespace -{ - -SemaphoreHandle_t s_storage_mutex = nullptr; -bool s_external_block_owner_active = false; - -bool ensure_storage_mutex() -{ - if (s_storage_mutex) - { - return true; - } - s_storage_mutex = xSemaphoreCreateRecursiveMutex(); - return s_storage_mutex != nullptr; -} - -class StorageLock final -{ - public: - StorageLock() - { - if (ensure_storage_mutex()) - { - locked_ = xSemaphoreTakeRecursive(s_storage_mutex, portMAX_DELAY) == pdTRUE; - } - } - - ~StorageLock() - { - if (locked_) - { - xSemaphoreGiveRecursive(s_storage_mutex); - } - } - - bool locked() const { return locked_; } - - StorageLock(const StorageLock&) = delete; - StorageLock& operator=(const StorageLock&) = delete; - - private: - bool locked_ = false; -}; - -bool path_is_safe(const char* path) -{ - if (!path || path[0] == '\0') - { - return false; - } - return std::strcmp(path, "..") != 0 && - std::strncmp(path, "../", 3) != 0 && - std::strstr(path, "/../") == nullptr && - !(std::strlen(path) >= 3 && - std::strcmp(path + std::strlen(path) - 3, "/..") == 0); -} - -bool physical_path(const char* logical_path, std::string& out) -{ - out.clear(); - if (!logical_path) - { - return false; - } - - const char* path = logical_path; - if ((path[0] == 'A' || path[0] == 'a') && path[1] == ':') - { - path += 2; - } - if (path[0] == '\0') - { - path = "/"; - } - if (!path_is_safe(path)) - { - return false; - } - - out = ::platform::esp::idf_common::flash_storage_runtime::mount_point(); - if (path[0] != '/') - { - out.push_back('/'); - } - out += path; - return true; -} - -bool ensure_ffat_ready() -{ - return ::platform::esp::idf_common::flash_storage_runtime::ensure_ready(true); -} - -bool mode_mutates(const char* mode) -{ - return mode && (std::strchr(mode, 'w') || - std::strchr(mode, 'a') || - std::strchr(mode, '+')); -} - -} // namespace - -bool mount_sd_card(int, SPIClass&, uint32_t, const char*, uint8_t) -{ - return false; -} - -void unmount_sd_card() -{ - // This adapter borrows the process-wide FFat mount. Its lifecycle belongs - // to flash_storage_runtime and must not be coupled to an SdStore instance. -} - -bool sd_card_ready() -{ - return ensure_ffat_ready(); -} - -bool sd_card_uses_sdfat() -{ - return false; -} - -bool sd_card_is_exfat() -{ - return false; -} - -SdCardBackend sd_card_backend() -{ - return SdCardBackend::None; -} - -SdCardInfo sd_card_info() -{ - SdCardInfo info{}; - info.backend = SdCardBackend::None; - return info; -} - -const char* sd_card_backend_name() -{ - return ensure_ffat_ready() ? "ffat" : "none"; -} - -const char* sd_card_filesystem_name() -{ - return ensure_ffat_ready() ? "FAT" : "none"; -} - -bool sd_external_block_owner_active() -{ - StorageLock lock; - return lock.locked() && s_external_block_owner_active; -} - -void sd_set_external_block_owner_active(bool active) -{ - StorageLock lock; - if (lock.locked()) - { - s_external_block_owner_active = active; - } -} - -bool sd_exists(const char* path) -{ - StorageLock lock; - std::string physical; - struct stat st - { - }; - return lock.locked() && ensure_ffat_ready() && physical_path(path, physical) && - ::stat(physical.c_str(), &st) == 0; -} - -bool sd_is_directory(const char* path) -{ - StorageLock lock; - std::string physical; - struct stat st - { - }; - return lock.locked() && ensure_ffat_ready() && physical_path(path, physical) && - ::stat(physical.c_str(), &st) == 0 && S_ISDIR(st.st_mode); -} - -bool sd_mkdir(const char* path) -{ - StorageLock lock; - std::string physical; - if (!lock.locked() || s_external_block_owner_active || !ensure_ffat_ready() || - !physical_path(path, physical)) - { - return false; - } - return ::mkdir(physical.c_str(), 0775) == 0 || errno == EEXIST; -} - -bool sd_rmdir(const char* path) -{ - StorageLock lock; - std::string physical; - return lock.locked() && !s_external_block_owner_active && ensure_ffat_ready() && - physical_path(path, physical) && ::rmdir(physical.c_str()) == 0; -} - -bool sd_remove(const char* path) -{ - StorageLock lock; - std::string physical; - return lock.locked() && !s_external_block_owner_active && ensure_ffat_ready() && - physical_path(path, physical) && std::remove(physical.c_str()) == 0; -} - -bool sd_rename(const char* old_path, const char* new_path) -{ - StorageLock lock; - std::string old_physical; - std::string new_physical; - return lock.locked() && !s_external_block_owner_active && ensure_ffat_ready() && - physical_path(old_path, old_physical) && - physical_path(new_path, new_physical) && - std::rename(old_physical.c_str(), new_physical.c_str()) == 0; -} - -class SdRuntimeFile::Impl -{ - public: - std::FILE* file = nullptr; -}; - -SdRuntimeFile::SdRuntimeFile() - : impl_(new (std::nothrow) Impl()) -{ -} - -SdRuntimeFile::~SdRuntimeFile() -{ - close(); - delete impl_; - impl_ = nullptr; -} - -bool SdRuntimeFile::open(const char* path, const char* mode) -{ - StorageLock lock; - std::string physical; - if (!lock.locked() || !impl_ || !ensure_ffat_ready() || - !physical_path(path, physical) || !mode || - (s_external_block_owner_active && mode_mutates(mode))) - { - return false; - } - if (impl_->file) - { - std::fclose(impl_->file); - impl_->file = nullptr; - } - impl_->file = std::fopen(physical.c_str(), mode); - return impl_->file != nullptr; -} - -void SdRuntimeFile::close() -{ - StorageLock lock; - if (lock.locked() && impl_ && impl_->file) - { - std::fclose(impl_->file); - impl_->file = nullptr; - } -} - -bool SdRuntimeFile::is_open() const -{ - StorageLock lock; - return lock.locked() && impl_ && impl_->file; -} - -int SdRuntimeFile::available() const -{ - StorageLock lock; - if (!lock.locked() || !impl_ || !impl_->file) - { - return 0; - } - const long current = std::ftell(impl_->file); - if (current < 0 || std::fseek(impl_->file, 0, SEEK_END) != 0) - { - return 0; - } - const long end = std::ftell(impl_->file); - (void)std::fseek(impl_->file, current, SEEK_SET); - if (end <= current) - { - return 0; - } - const long remaining = end - current; - return remaining > std::numeric_limits::max() - ? std::numeric_limits::max() - : static_cast(remaining); -} - -int SdRuntimeFile::read(void* buffer, std::size_t bytes_to_read) -{ - StorageLock lock; - if (!lock.locked() || !impl_ || !impl_->file || (!buffer && bytes_to_read > 0)) - { - return -1; - } - const std::size_t count = std::fread(buffer, 1, bytes_to_read, impl_->file); - return count > static_cast(std::numeric_limits::max()) - ? std::numeric_limits::max() - : static_cast(count); -} - -int SdRuntimeFile::read_byte() -{ - uint8_t value = 0; - return read(&value, 1) == 1 ? value : -1; -} - -std::size_t SdRuntimeFile::read_bytes(char* buffer, std::size_t bytes_to_read) -{ - const int count = read(buffer, bytes_to_read); - return count > 0 ? static_cast(count) : 0; -} - -std::size_t SdRuntimeFile::write(const void* buffer, std::size_t bytes_to_write) -{ - StorageLock lock; - if (!lock.locked() || !impl_ || !impl_->file || s_external_block_owner_active || - (!buffer && bytes_to_write > 0)) - { - return 0; - } - return std::fwrite(buffer, 1, bytes_to_write, impl_->file); -} - -std::size_t SdRuntimeFile::write_byte(uint8_t value) -{ - return write(&value, 1); -} - -std::size_t SdRuntimeFile::print(const char* text) -{ - return text ? write(text, std::strlen(text)) : 0; -} - -std::size_t SdRuntimeFile::print(double value, int digits) -{ - char text[48] = {}; - const int count = std::snprintf(text, sizeof(text), "%.*f", digits, value); - return count > 0 ? write(text, static_cast(count)) : 0; -} - -std::size_t SdRuntimeFile::printf(const char* format, ...) -{ - if (!format) - { - return 0; - } - char text[256] = {}; - va_list args; - va_start(args, format); - const int count = std::vsnprintf(text, sizeof(text), format, args); - va_end(args); - if (count <= 0) - { - return 0; - } - return write(text, std::min(static_cast(count), sizeof(text) - 1)); -} - -bool SdRuntimeFile::seek(uint64_t offset) -{ - StorageLock lock; - return lock.locked() && impl_ && impl_->file && - offset <= static_cast(std::numeric_limits::max()) && - std::fseek(impl_->file, static_cast(offset), SEEK_SET) == 0; -} - -uint64_t SdRuntimeFile::position() const -{ - StorageLock lock; - if (!lock.locked() || !impl_ || !impl_->file) - { - return 0; - } - const long position = std::ftell(impl_->file); - return position >= 0 ? static_cast(position) : 0; -} - -uint64_t SdRuntimeFile::size() const -{ - StorageLock lock; - if (!lock.locked() || !impl_ || !impl_->file) - { - return 0; - } - const long current = std::ftell(impl_->file); - if (current < 0 || std::fseek(impl_->file, 0, SEEK_END) != 0) - { - return 0; - } - const long end = std::ftell(impl_->file); - (void)std::fseek(impl_->file, current, SEEK_SET); - return end >= 0 ? static_cast(end) : 0; -} - -bool SdRuntimeFile::flush() -{ - StorageLock lock; - return lock.locked() && impl_ && impl_->file && std::fflush(impl_->file) == 0; -} - -class SdRuntimeDir::Impl -{ - public: - DIR* dir = nullptr; - std::string physical_path{}; -}; - -SdRuntimeDir::SdRuntimeDir() - : impl_(new (std::nothrow) Impl()) -{ -} - -SdRuntimeDir::~SdRuntimeDir() -{ - close(); - delete impl_; - impl_ = nullptr; -} - -bool SdRuntimeDir::open(const char* path) -{ - StorageLock lock; - std::string physical; - if (!lock.locked() || !impl_ || !ensure_ffat_ready() || !physical_path(path, physical)) - { - return false; - } - if (impl_->dir) - { - ::closedir(impl_->dir); - impl_->dir = nullptr; - } - impl_->dir = ::opendir(physical.c_str()); - if (!impl_->dir) - { - impl_->physical_path.clear(); - return false; - } - impl_->physical_path = std::move(physical); - return true; -} - -void SdRuntimeDir::close() -{ - StorageLock lock; - if (lock.locked() && impl_ && impl_->dir) - { - ::closedir(impl_->dir); - impl_->dir = nullptr; - impl_->physical_path.clear(); - } -} - -bool SdRuntimeDir::is_open() const -{ - StorageLock lock; - return lock.locked() && impl_ && impl_->dir; -} - -bool SdRuntimeDir::read_next(char* name, std::size_t name_size, bool* is_dir) -{ - StorageLock lock; - if (!lock.locked() || !impl_ || !impl_->dir || !name || name_size == 0) - { - return false; - } - while (dirent* entry = ::readdir(impl_->dir)) - { - if (std::strcmp(entry->d_name, ".") == 0 || std::strcmp(entry->d_name, "..") == 0) - { - continue; - } - std::snprintf(name, name_size, "%s", entry->d_name); - if (is_dir) - { - const std::string path = impl_->physical_path + "/" + entry->d_name; - struct stat st - { - }; - *is_dir = ::stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode); - } - return true; - } - return false; -} - -bool sd_read_raw(uint32_t, uint8_t*) -{ - return false; -} - -bool sd_write_raw(uint32_t, const uint8_t*) -{ - return false; -} - -} // namespace platform::esp::arduino_common::storage diff --git a/platform/esp/idf_common/src/sd_card_runtime_sdfat_adapter.cpp b/platform/esp/idf_common/src/sd_card_runtime_sdfat_adapter.cpp new file mode 100644 index 00000000..68338dd6 --- /dev/null +++ b/platform/esp/idf_common/src/sd_card_runtime_sdfat_adapter.cpp @@ -0,0 +1,1270 @@ +#include "platform/esp/idf_common/sd_card_runtime_sdfat_adapter.h" + +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" + +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if SDFAT_FILE_TYPE != 3 +#error "TrailMate IDF SD runtime requires SdFat with FAT/FAT32/exFAT support." +#endif + +#if !USE_BLOCK_DEVICE_INTERFACE +#error "TrailMate IDF SD runtime requires SdFat FsBlockDeviceInterface support." +#endif + +namespace platform::esp::arduino_common::storage +{ +namespace detail +{ + +namespace sdmmc_host_runtime = ::platform::esp::idf_common::sdmmc_host_runtime; + +constexpr const char* kTag = "idf-sdfat"; +constexpr uint8_t kRuntimeCardNone = 0; +constexpr uint8_t kRuntimeCardSdhc = 3; +constexpr uint8_t kRuntimeCardUnknown = 4; +constexpr uint32_t kSdSectorSize = 512; +constexpr TickType_t kSdRuntimeLockWait = pdMS_TO_TICKS(250); + +#ifndef TRAIL_MATE_SD_IO_LOG_ENABLE +#define TRAIL_MATE_SD_IO_LOG_ENABLE 1 +#endif + +#ifndef TRAIL_MATE_SD_IO_TRACE_LOG +#define TRAIL_MATE_SD_IO_TRACE_LOG 0 +#endif + +#ifndef TRAIL_MATE_SD_IO_SLOW_MS +#define TRAIL_MATE_SD_IO_SLOW_MS 20 +#endif + +#ifndef TRAIL_MATE_SD_IO_LOG_INTERVAL_MS +#define TRAIL_MATE_SD_IO_LOG_INTERVAL_MS 1000 +#endif + +FsVolume s_volume; +SdCardInfo s_info{}; +sdmmc_card_t* s_card = nullptr; +sdmmc_host_t s_host = SDMMC_HOST_DEFAULT(); +sdmmc_host_runtime::SlotOwner s_owner = sdmmc_host_runtime::SlotOwner::None; +bool s_mounted = false; +volatile bool s_external_block_owner_active = false; +SemaphoreHandle_t s_storage_mutex = nullptr; +uint32_t s_last_sd_io_log_ms = 0; +uint32_t s_suppressed_sd_io_logs = 0; +alignas(4) DRAM_ATTR uint8_t s_dma_sector[kSdSectorSize]; + +uint32_t now_ms() +{ + return static_cast(esp_timer_get_time() / 1000ULL); +} + +bool ensure_storage_mutex() +{ + if (s_storage_mutex) + { + return true; + } + s_storage_mutex = xSemaphoreCreateRecursiveMutex(); + return s_storage_mutex != nullptr; +} + +class SdRuntimeBusGuard +{ + public: + explicit SdRuntimeBusGuard(const char* owner = "sd_runtime") + : owner_(owner) + { + if (ensure_storage_mutex()) + { + locked_ = xSemaphoreTakeRecursive(s_storage_mutex, kSdRuntimeLockWait) == pdTRUE; + } + if (!locked_) + { + ESP_LOGW(kTag, "storage lock timeout owner=%s", owner_ ? owner_ : "unknown"); + } + } + + ~SdRuntimeBusGuard() + { + if (locked_) + { + xSemaphoreGiveRecursive(s_storage_mutex); + } + } + + bool locked() const { return locked_; } + + SdRuntimeBusGuard(const SdRuntimeBusGuard&) = delete; + SdRuntimeBusGuard& operator=(const SdRuntimeBusGuard&) = delete; + + private: + const char* owner_ = nullptr; + bool locked_ = false; +}; + +const char* backend_name_from_info() +{ + switch (s_info.backend) + { + case SdCardBackend::SdFat: + return "sdfat"; + case SdCardBackend::None: + default: + return "none"; + } +} + +const char* safe_path(const char* path) +{ + return path ? path : ""; +} + +void copy_path(char* out, std::size_t out_size, const char* path) +{ + if (!out || out_size == 0) + { + return; + } + std::snprintf(out, out_size, "%s", safe_path(path)); +} + +bool path_empty(const char* path) +{ + return path == nullptr || path[0] == '\0'; +} + +const char* normalize_sd_path(const char* path) +{ + if (path_empty(path)) + { + return "/"; + } + + if ((path[0] == 'A' || path[0] == 'a') && path[1] == ':') + { + path += 2; + } + + if (path[0] == '\0') + { + return "/"; + } + return path; +} + +bool open_mode_mutates(const char* mode) +{ + if (mode == nullptr) + { + return false; + } + return std::strchr(mode, 'w') != nullptr || std::strchr(mode, 'a') != nullptr || + std::strchr(mode, '+') != nullptr; +} + +uint32_t sd_io_begin(const char* op, const char* path, std::size_t bytes = 0) +{ + const uint32_t start_ms = now_ms(); +#if TRAIL_MATE_SD_IO_LOG_ENABLE && TRAIL_MATE_SD_IO_TRACE_LOG + ESP_LOGI(kTag, + "io begin op=%s backend=%s path=%s bytes=%u t=%lu", + op ? op : "unknown", + backend_name_from_info(), + safe_path(path), + static_cast(bytes), + static_cast(start_ms)); +#else + (void)op; + (void)path; + (void)bytes; +#endif + return start_ms; +} + +void sd_io_end(const char* op, + const char* path, + uint32_t start_ms, + bool ok, + std::size_t bytes = 0, + int32_t result = 0) +{ + const uint32_t end_ms = now_ms(); + const uint32_t elapsed_ms = end_ms - start_ms; +#if TRAIL_MATE_SD_IO_LOG_ENABLE + if (TRAIL_MATE_SD_IO_TRACE_LOG || !ok || elapsed_ms >= TRAIL_MATE_SD_IO_SLOW_MS) + { + ++s_suppressed_sd_io_logs; + if (TRAIL_MATE_SD_IO_TRACE_LOG || s_last_sd_io_log_ms == 0 || + end_ms - s_last_sd_io_log_ms >= TRAIL_MATE_SD_IO_LOG_INTERVAL_MS) + { + ESP_LOGI(kTag, + "io end op=%s backend=%s path=%s ok=%d bytes=%u result=%ld elapsed_ms=%lu suppressed=%lu", + op ? op : "unknown", + backend_name_from_info(), + safe_path(path), + ok ? 1 : 0, + static_cast(bytes), + static_cast(result), + static_cast(elapsed_ms), + static_cast(s_suppressed_sd_io_logs - 1)); + s_suppressed_sd_io_logs = 0; + s_last_sd_io_log_ms = end_ms; + } + } +#else + (void)op; + (void)path; + (void)ok; + (void)bytes; + (void)result; + (void)elapsed_ms; +#endif +} + +bool sd_mutation_blocked_by_external_owner(const char* op, + const char* path, + uint32_t start_ms, + std::size_t bytes = 0) +{ + if (!s_external_block_owner_active) + { + return false; + } + sd_io_end(op, path, start_ms, false, bytes, -4); + return true; +} + +oflag_t sdfat_open_flags(const char* mode) +{ + if (mode == nullptr || std::strcmp(mode, "r") == 0 || std::strcmp(mode, "rb") == 0) + { + return O_RDONLY; + } + if (std::strcmp(mode, "w") == 0 || std::strcmp(mode, "wb") == 0) + { + return O_WRONLY | O_CREAT | O_TRUNC; + } + if (std::strcmp(mode, "a") == 0 || std::strcmp(mode, "ab") == 0) + { + return O_WRONLY | O_CREAT | O_APPEND; + } + if (std::strcmp(mode, "r+") == 0 || std::strcmp(mode, "rb+") == 0 || + std::strcmp(mode, "r+b") == 0) + { + return O_RDWR; + } + if (std::strcmp(mode, "w+") == 0 || std::strcmp(mode, "wb+") == 0 || + std::strcmp(mode, "w+b") == 0) + { + return O_RDWR | O_CREAT | O_TRUNC; + } + if (std::strcmp(mode, "a+") == 0 || std::strcmp(mode, "ab+") == 0 || + std::strcmp(mode, "a+b") == 0) + { + return O_RDWR | O_CREAT | O_APPEND; + } + return O_RDONLY; +} + +class SdmmcBlockDevice final : public FsBlockDeviceInterface +{ + public: + void setCard(sdmmc_card_t* card) { card_ = card; } + + void end() override { card_ = nullptr; } + + bool isBusy() override { return false; } + + bool readSector(Sector_t sector, uint8_t* dst) override + { + if (!card_ || !dst) + { + return false; + } + const esp_err_t err = sdmmc_read_sectors(card_, s_dma_sector, sector, 1); + if (err != ESP_OK) + { + ESP_LOGW(kTag, + "raw read sector=%lu failed: %s", + static_cast(sector), + esp_err_to_name(err)); + return false; + } + std::memcpy(dst, s_dma_sector, kSdSectorSize); + return true; + } + + bool readSectors(Sector_t sector, uint8_t* dst, size_t ns) override + { + if (!card_ || (!dst && ns > 0)) + { + return false; + } + for (size_t i = 0; i < ns; ++i) + { + if (!readSector(sector + static_cast(i), + dst + (i * kSdSectorSize))) + { + return false; + } + } + return true; + } + + Sector_t sectorCount() override + { + return card_ ? static_cast(card_->csd.capacity) : 0; + } + + bool syncDevice() override { return card_ != nullptr; } + + bool writeSector(Sector_t sector, const uint8_t* src) override + { + if (!card_ || !src) + { + return false; + } + std::memcpy(s_dma_sector, src, kSdSectorSize); + const esp_err_t err = sdmmc_write_sectors(card_, s_dma_sector, sector, 1); + if (err != ESP_OK) + { + ESP_LOGW(kTag, + "raw write sector=%lu failed: %s", + static_cast(sector), + esp_err_to_name(err)); + return false; + } + return true; + } + + bool writeSectors(Sector_t sector, const uint8_t* src, size_t ns) override + { + if (!card_ || (!src && ns > 0)) + { + return false; + } + for (size_t i = 0; i < ns; ++i) + { + if (!writeSector(sector + static_cast(i), + src + (i * kSdSectorSize))) + { + return false; + } + } + return true; + } + + private: + sdmmc_card_t* card_ = nullptr; +}; + +SdmmcBlockDevice s_block_device; + +void reset_info_locked() +{ + s_info = SdCardInfo{}; +} + +uint8_t card_type_from_idf(const sdmmc_card_t* card) +{ + if (!card) + { + return kRuntimeCardNone; + } + return card->csd.capacity > 0 ? kRuntimeCardSdhc : kRuntimeCardUnknown; +} + +void clear_mounted_locked() +{ + if (s_mounted) + { + s_volume.end(); + } + s_block_device.end(); + if (s_card) + { + const int slot = s_host.slot; + (void)sdmmc_host_runtime::release_slot(s_owner, slot); + std::free(s_card); + s_card = nullptr; + } + s_owner = sdmmc_host_runtime::SlotOwner::None; + s_mounted = false; + s_external_block_owner_active = false; + reset_info_locked(); +} + +void record_sdfat_info_locked() +{ + const uint32_t info_start_ms = now_ms(); + s_info = SdCardInfo{}; + s_info.backend = SdCardBackend::SdFat; + s_info.card_type = card_type_from_idf(s_card); + s_info.fat_type = s_volume.fatType(); + s_info.sector_size = kSdSectorSize; + s_info.sector_count = static_cast(s_block_device.sectorCount()); + s_info.card_size_bytes = static_cast(s_info.sector_count) * kSdSectorSize; + + const uint64_t cluster_count = s_volume.clusterCount(); + const uint64_t bytes_per_cluster = s_volume.bytesPerCluster(); + if (cluster_count > 0 && bytes_per_cluster > 0) + { + s_info.total_bytes = cluster_count * bytes_per_cluster; + } + + ESP_LOGI(kTag, + "info card_type=%u fat=%u sectors=%lu card_mb=%llu total_mb=%llu elapsed_ms=%lu", + static_cast(s_info.card_type), + static_cast(s_info.fat_type), + static_cast(s_info.sector_count), + static_cast(s_info.card_size_bytes / (1024ULL * 1024ULL)), + static_cast(s_info.total_bytes / (1024ULL * 1024ULL)), + static_cast(now_ms() - info_start_ms)); +} + +bool mount_sdmmc_locked(sdmmc_host_runtime::SlotOwner owner, + const sdmmc_host_t& host, + const sdmmc_slot_config_t& slot_config, + const char* mount_point, + uint8_t max_files) +{ + (void)mount_point; + (void)max_files; + + if (s_mounted) + { + return true; + } + + s_host = host; + s_owner = owner; + s_card = static_cast(std::calloc(1, sizeof(sdmmc_card_t))); + if (!s_card) + { + clear_mounted_locked(); + return false; + } + + esp_err_t err = sdmmc_host_runtime::initialize_slot(owner, s_host, slot_config); + if (err != ESP_OK) + { + ESP_LOGW(kTag, + "SDMMC slot init failed owner=%s slot=%d err=%s", + sdmmc_host_runtime::owner_name(owner), + s_host.slot, + esp_err_to_name(err)); + clear_mounted_locked(); + return false; + } + + err = sdmmc_card_init(&s_host, s_card); + if (err != ESP_OK) + { + ESP_LOGW(kTag, + "SDMMC card init failed owner=%s slot=%d err=%s", + sdmmc_host_runtime::owner_name(owner), + s_host.slot, + esp_err_to_name(err)); + clear_mounted_locked(); + return false; + } + + s_block_device.setCard(s_card); + bool volume_ok = s_volume.begin(&s_block_device, true, 1); + if (!volume_ok) + { + ESP_LOGW(kTag, "SdFat partition mount failed; retrying as superfloppy"); + volume_ok = s_volume.begin(&s_block_device, true, 0); + } + if (!volume_ok || s_volume.fatType() == 0) + { + ESP_LOGW(kTag, "SdFat volume mount failed fat=%u", static_cast(s_volume.fatType())); + clear_mounted_locked(); + return false; + } + + s_mounted = true; + record_sdfat_info_locked(); + ESP_LOGI(kTag, + "backend=sdfat host=sdmmc slot=%d owner=%s fs=%s card=%llu MB total=%llu MB sectors=%lu", + s_host.slot, + sdmmc_host_runtime::owner_name(owner), + sd_card_filesystem_name(), + static_cast(s_info.card_size_bytes / (1024ULL * 1024ULL)), + static_cast(s_info.total_bytes / (1024ULL * 1024ULL)), + static_cast(s_info.sector_count)); + return true; +} + +} // namespace detail + +using namespace detail; + +bool mount_sd_card(int, SPIClass&, uint32_t, const char*, uint8_t) +{ + ESP_LOGW(kTag, "SPI SdFat mount is unavailable in ESP-IDF build; use native SDMMC mount"); + return false; +} + +void unmount_sd_card() +{ + SdRuntimeBusGuard guard("sd_unmount"); + if (!guard.locked()) + { + return; + } + clear_mounted_locked(); +} + +bool sd_card_ready() +{ + SdRuntimeBusGuard guard("sd_ready"); + return guard.locked() && s_mounted && s_info.backend == SdCardBackend::SdFat && + s_info.sector_size != 0 && s_info.card_type != kRuntimeCardNone; +} + +bool sd_card_uses_sdfat() +{ + SdRuntimeBusGuard guard("sd_backend"); + return guard.locked() && s_info.backend == SdCardBackend::SdFat; +} + +bool sd_card_is_exfat() +{ + SdRuntimeBusGuard guard("sd_exfat"); + return guard.locked() && s_info.backend == SdCardBackend::SdFat && + s_info.fat_type == FAT_TYPE_EXFAT; +} + +SdCardBackend sd_card_backend() +{ + SdRuntimeBusGuard guard("sd_backend"); + return guard.locked() ? s_info.backend : SdCardBackend::None; +} + +SdCardInfo sd_card_info() +{ + SdRuntimeBusGuard guard("sd_info"); + return guard.locked() ? s_info : SdCardInfo{}; +} + +const char* sd_card_backend_name() +{ + return backend_name_from_info(); +} + +const char* sd_card_filesystem_name() +{ + SdRuntimeBusGuard guard("sd_fs_name"); + if (!guard.locked()) + { + return "none"; + } + if (s_info.backend == SdCardBackend::SdFat) + { + switch (s_info.fat_type) + { + case FAT_TYPE_EXFAT: + return "exfat"; + case FAT_TYPE_FAT32: + return "fat32"; + case FAT_TYPE_FAT16: + return "fat16"; + case FAT_TYPE_FAT12: + return "fat12"; + default: + return "fat"; + } + } + return "none"; +} + +bool sd_external_block_owner_active() +{ + return s_external_block_owner_active; +} + +void sd_set_external_block_owner_active(bool active) +{ + s_external_block_owner_active = active; +} + +bool sd_exists(const char* path) +{ + const char* normalized = normalize_sd_path(path); + const uint32_t start_ms = sd_io_begin("exists", normalized); + bool result = false; + SdRuntimeBusGuard guard("sd_exists"); + if (!guard.locked()) + { + sd_io_end("exists", normalized, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + result = s_volume.exists(normalized); + sd_io_end("exists", normalized, start_ms, true, 0, result ? 1 : 0); + return result; + } + sd_io_end("exists", normalized, start_ms, false, 0, -1); + return false; +} + +bool sd_is_directory(const char* path) +{ + const char* normalized = normalize_sd_path(path); + const uint32_t start_ms = sd_io_begin("is_dir", normalized); + bool result = false; + SdRuntimeBusGuard guard("sd_is_dir"); + if (!guard.locked()) + { + sd_io_end("is_dir", normalized, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + FsFile dir = s_volume.open(normalized, O_RDONLY); + result = dir && dir.isDir(); + dir.close(); + sd_io_end("is_dir", normalized, start_ms, true, 0, result ? 1 : 0); + return result; + } + sd_io_end("is_dir", normalized, start_ms, false, 0, -1); + return false; +} + +bool sd_mkdir(const char* path) +{ + const char* normalized = normalize_sd_path(path); + const uint32_t start_ms = sd_io_begin("mkdir", normalized); + if (sd_mutation_blocked_by_external_owner("mkdir", normalized, start_ms)) + { + return false; + } + bool result = false; + SdRuntimeBusGuard guard("sd_mkdir"); + if (!guard.locked()) + { + sd_io_end("mkdir", normalized, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + result = s_volume.mkdir(normalized, true) || s_volume.exists(normalized); + sd_io_end("mkdir", normalized, start_ms, result); + return result; + } + sd_io_end("mkdir", normalized, start_ms, false, 0, -1); + return false; +} + +bool sd_rmdir(const char* path) +{ + const char* normalized = normalize_sd_path(path); + const uint32_t start_ms = sd_io_begin("rmdir", normalized); + if (sd_mutation_blocked_by_external_owner("rmdir", normalized, start_ms)) + { + return false; + } + bool result = false; + SdRuntimeBusGuard guard("sd_rmdir"); + if (!guard.locked()) + { + sd_io_end("rmdir", normalized, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + result = s_volume.rmdir(normalized); + sd_io_end("rmdir", normalized, start_ms, result); + return result; + } + sd_io_end("rmdir", normalized, start_ms, false, 0, -1); + return false; +} + +bool sd_remove(const char* path) +{ + const char* normalized = normalize_sd_path(path); + const uint32_t start_ms = sd_io_begin("remove", normalized); + if (sd_mutation_blocked_by_external_owner("remove", normalized, start_ms)) + { + return false; + } + bool result = false; + SdRuntimeBusGuard guard("sd_remove"); + if (!guard.locked()) + { + sd_io_end("remove", normalized, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + result = s_volume.remove(normalized); + sd_io_end("remove", normalized, start_ms, result); + return result; + } + sd_io_end("remove", normalized, start_ms, false, 0, -1); + return false; +} + +bool sd_rename(const char* old_path, const char* new_path) +{ + const char* normalized_old = normalize_sd_path(old_path); + const char* normalized_new = normalize_sd_path(new_path); + const uint32_t start_ms = sd_io_begin("rename", normalized_old); + if (sd_mutation_blocked_by_external_owner("rename", normalized_old, start_ms)) + { + return false; + } + bool result = false; + SdRuntimeBusGuard guard("sd_rename"); + if (!guard.locked()) + { + sd_io_end("rename", normalized_old, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + result = s_volume.rename(normalized_old, normalized_new); + sd_io_end("rename", normalized_old, start_ms, result, 0, result ? 0 : -1); + return result; + } + sd_io_end("rename", normalized_old, start_ms, false, 0, -1); + return false; +} + +class SdRuntimeFile::Impl +{ + public: + FsFile sdfat_file; + SdCardBackend backend = SdCardBackend::None; + char path[128]{}; + char mode[8]{}; +}; + +SdRuntimeFile::SdRuntimeFile() + : impl_(new (std::nothrow) Impl()) +{ +} + +SdRuntimeFile::~SdRuntimeFile() +{ + close(); + delete impl_; +} + +bool SdRuntimeFile::open(const char* path, const char* mode) +{ + close(); + if (impl_ == nullptr || path_empty(path)) + { + return false; + } + + const char* normalized = normalize_sd_path(path); + copy_path(impl_->path, sizeof(impl_->path), normalized); + copy_path(impl_->mode, sizeof(impl_->mode), mode ? mode : "r"); + const uint32_t start_ms = sd_io_begin("file_open", impl_->path); + if (open_mode_mutates(mode) && + sd_mutation_blocked_by_external_owner("file_open", impl_->path, start_ms)) + { + return false; + } + SdRuntimeBusGuard guard("sd_file_open"); + if (!guard.locked()) + { + sd_io_end("file_open", impl_->path, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + impl_->sdfat_file = s_volume.open(normalized, sdfat_open_flags(mode)); + impl_->backend = impl_->sdfat_file ? SdCardBackend::SdFat : SdCardBackend::None; + sd_io_end("file_open", impl_->path, start_ms, impl_->backend == SdCardBackend::SdFat); + return impl_->backend == SdCardBackend::SdFat; + } + + sd_io_end("file_open", impl_->path, start_ms, false, 0, -1); + return false; +} + +void SdRuntimeFile::close() +{ + if (impl_ == nullptr) + { + return; + } + if (impl_->backend == SdCardBackend::SdFat) + { + const uint32_t start_ms = sd_io_begin("file_close", impl_->path); + SdRuntimeBusGuard guard("sd_file_close"); + if (guard.locked()) + { + impl_->sdfat_file.close(); + sd_io_end("file_close", impl_->path, start_ms, true); + } + else + { + sd_io_end("file_close", impl_->path, start_ms, false, 0, -2); + } + } + impl_->backend = SdCardBackend::None; + impl_->path[0] = '\0'; + impl_->mode[0] = '\0'; +} + +bool SdRuntimeFile::is_open() const +{ + return impl_ != nullptr && impl_->backend != SdCardBackend::None; +} + +int SdRuntimeFile::available() const +{ + if (!is_open()) + { + return 0; + } + if (impl_->backend == SdCardBackend::SdFat) + { + SdRuntimeBusGuard guard("sd_file_available"); + if (!guard.locked()) + { + return 0; + } + return impl_->sdfat_file.available(); + } + return 0; +} + +int SdRuntimeFile::read(void* buffer, std::size_t bytes_to_read) +{ + if (!is_open() || buffer == nullptr || bytes_to_read == 0) + { + return 0; + } + if (impl_->backend == SdCardBackend::SdFat) + { + const uint32_t start_ms = sd_io_begin("file_read", impl_->path, bytes_to_read); + SdRuntimeBusGuard guard("sd_file_read"); + if (!guard.locked()) + { + sd_io_end("file_read", impl_->path, start_ms, false, bytes_to_read, -2); + return -1; + } + const int result = impl_->sdfat_file.read(buffer, bytes_to_read); + sd_io_end("file_read", impl_->path, start_ms, result >= 0, bytes_to_read, result); + return result; + } + return -1; +} + +int SdRuntimeFile::read_byte() +{ + if (!is_open()) + { + return -1; + } + if (impl_->backend == SdCardBackend::SdFat) + { + SdRuntimeBusGuard guard("sd_file_read_byte"); + if (!guard.locked()) + { + return -1; + } + return impl_->sdfat_file.read(); + } + return -1; +} + +std::size_t SdRuntimeFile::read_bytes(char* buffer, std::size_t bytes_to_read) +{ + if (!is_open() || buffer == nullptr || bytes_to_read == 0) + { + return 0; + } + if (impl_->backend == SdCardBackend::SdFat) + { + const uint32_t start_ms = sd_io_begin("file_read_bytes", impl_->path, bytes_to_read); + SdRuntimeBusGuard guard("sd_file_read_bytes"); + if (!guard.locked()) + { + sd_io_end("file_read_bytes", impl_->path, start_ms, false, bytes_to_read, -2); + return 0; + } + int result = impl_->sdfat_file.read(buffer, bytes_to_read); + sd_io_end("file_read_bytes", impl_->path, start_ms, result >= 0, bytes_to_read, result); + return result > 0 ? static_cast(result) : 0; + } + return 0; +} + +std::size_t SdRuntimeFile::write(const void* buffer, std::size_t bytes_to_write) +{ + if (!is_open() || buffer == nullptr || bytes_to_write == 0) + { + return 0; + } + if (impl_->backend == SdCardBackend::SdFat) + { + const uint32_t start_ms = sd_io_begin("file_write", impl_->path, bytes_to_write); + if (sd_mutation_blocked_by_external_owner( + "file_write", impl_->path, start_ms, bytes_to_write)) + { + return 0; + } + SdRuntimeBusGuard guard("sd_file_write"); + if (!guard.locked()) + { + sd_io_end("file_write", impl_->path, start_ms, false, bytes_to_write, -2); + return 0; + } + const std::size_t result = impl_->sdfat_file.write(buffer, bytes_to_write); + sd_io_end("file_write", impl_->path, start_ms, result == bytes_to_write, bytes_to_write, result); + return result; + } + return 0; +} + +std::size_t SdRuntimeFile::write_byte(uint8_t value) +{ + return write(&value, 1); +} + +std::size_t SdRuntimeFile::print(const char* text) +{ + return text ? write(text, std::strlen(text)) : 0; +} + +std::size_t SdRuntimeFile::print(double value, int digits) +{ + char text[48] = {}; + const int count = std::snprintf(text, sizeof(text), "%.*f", digits < 0 ? 0 : digits, value); + return count > 0 ? write(text, static_cast(count)) : 0; +} + +std::size_t SdRuntimeFile::printf(const char* format, ...) +{ + if (!is_open() || format == nullptr) + { + return 0; + } + + va_list args; + va_start(args, format); + va_list args_copy; + va_copy(args_copy, args); + int len = std::vsnprintf(nullptr, 0, format, args_copy); + va_end(args_copy); + if (len <= 0) + { + va_end(args); + return 0; + } + + std::vector buffer(static_cast(len) + 1U); + std::vsnprintf(buffer.data(), buffer.size(), format, args); + va_end(args); + return write(buffer.data(), static_cast(len)); +} + +bool SdRuntimeFile::seek(uint64_t offset) +{ + if (!is_open()) + { + return false; + } + if (impl_->backend == SdCardBackend::SdFat) + { + SdRuntimeBusGuard guard("sd_file_seek"); + if (!guard.locked()) + { + return false; + } + return impl_->sdfat_file.seekSet(offset); + } + return false; +} + +uint64_t SdRuntimeFile::position() const +{ + if (!is_open()) + { + return 0; + } + if (impl_->backend == SdCardBackend::SdFat) + { + SdRuntimeBusGuard guard("sd_file_position"); + if (!guard.locked()) + { + return 0; + } + return impl_->sdfat_file.curPosition(); + } + return 0; +} + +uint64_t SdRuntimeFile::size() const +{ + if (!is_open()) + { + return 0; + } + if (impl_->backend == SdCardBackend::SdFat) + { + SdRuntimeBusGuard guard("sd_file_size"); + if (!guard.locked()) + { + return 0; + } + return impl_->sdfat_file.fileSize(); + } + return 0; +} + +bool SdRuntimeFile::flush() +{ + if (!is_open()) + { + return false; + } + if (impl_->backend == SdCardBackend::SdFat) + { + const uint32_t start_ms = sd_io_begin("file_flush", impl_->path); + if (sd_mutation_blocked_by_external_owner("file_flush", impl_->path, start_ms)) + { + return false; + } + SdRuntimeBusGuard guard("sd_file_flush"); + if (!guard.locked()) + { + sd_io_end("file_flush", impl_->path, start_ms, false, 0, -2); + return false; + } + const bool result = impl_->sdfat_file.sync(); + sd_io_end("file_flush", impl_->path, start_ms, result); + return result; + } + return false; +} + +class SdRuntimeDir::Impl +{ + public: + FsFile sdfat_dir; + SdCardBackend backend = SdCardBackend::None; + char path[128]{}; +}; + +SdRuntimeDir::SdRuntimeDir() + : impl_(new (std::nothrow) Impl()) +{ +} + +SdRuntimeDir::~SdRuntimeDir() +{ + close(); + delete impl_; +} + +bool SdRuntimeDir::open(const char* path) +{ + close(); + if (impl_ == nullptr) + { + return false; + } + const char* normalized = normalize_sd_path(path); + copy_path(impl_->path, sizeof(impl_->path), normalized); + const uint32_t start_ms = sd_io_begin("dir_open", impl_->path); + SdRuntimeBusGuard guard("sd_dir_open"); + if (!guard.locked()) + { + sd_io_end("dir_open", impl_->path, start_ms, false, 0, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + impl_->sdfat_dir = s_volume.open(normalized, O_RDONLY); + impl_->backend = + (impl_->sdfat_dir && impl_->sdfat_dir.isDir()) ? SdCardBackend::SdFat + : SdCardBackend::None; + sd_io_end("dir_open", impl_->path, start_ms, impl_->backend == SdCardBackend::SdFat); + return impl_->backend == SdCardBackend::SdFat; + } + sd_io_end("dir_open", impl_->path, start_ms, false, 0, -1); + return false; +} + +void SdRuntimeDir::close() +{ + if (impl_ == nullptr) + { + return; + } + if (impl_->backend == SdCardBackend::SdFat) + { + const uint32_t start_ms = sd_io_begin("dir_close", impl_->path); + SdRuntimeBusGuard guard("sd_dir_close"); + if (guard.locked()) + { + impl_->sdfat_dir.close(); + sd_io_end("dir_close", impl_->path, start_ms, true); + } + else + { + sd_io_end("dir_close", impl_->path, start_ms, false, 0, -2); + } + } + impl_->backend = SdCardBackend::None; + impl_->path[0] = '\0'; +} + +bool SdRuntimeDir::is_open() const +{ + return impl_ != nullptr && impl_->backend != SdCardBackend::None; +} + +bool SdRuntimeDir::read_next(char* name, std::size_t name_size, bool* is_dir) +{ + if (!is_open() || name == nullptr || name_size == 0) + { + return false; + } + name[0] = '\0'; + if (is_dir != nullptr) + { + *is_dir = false; + } + + if (impl_->backend == SdCardBackend::SdFat) + { + const uint32_t start_ms = sd_io_begin("dir_read", impl_->path); + SdRuntimeBusGuard guard("sd_dir_read"); + if (!guard.locked()) + { + sd_io_end("dir_read", impl_->path, start_ms, false, 0, -2); + return false; + } + FsFile entry = impl_->sdfat_dir.openNextFile(O_RDONLY); + if (!entry) + { + sd_io_end("dir_read", impl_->path, start_ms, true, 0, 0); + return false; + } + entry.getName(name, name_size); + if (is_dir != nullptr) + { + *is_dir = entry.isDir(); + } + entry.close(); + sd_io_end("dir_read", impl_->path, start_ms, true, 0, name[0] != '\0' ? 1 : 0); + return name[0] != '\0'; + } + + return false; +} + +bool sd_read_raw(uint32_t lba, uint8_t* buffer) +{ + char path[32]; + std::snprintf(path, sizeof(path), "raw:%lu", static_cast(lba)); + const uint32_t start_ms = sd_io_begin("raw_read", path, kSdSectorSize); + bool result = false; + SdRuntimeBusGuard guard("sd_raw_read"); + if (!guard.locked()) + { + sd_io_end("raw_read", path, start_ms, false, kSdSectorSize, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + result = s_block_device.readSector(lba, buffer); + sd_io_end("raw_read", path, start_ms, result, kSdSectorSize); + return result; + } + sd_io_end("raw_read", path, start_ms, false, kSdSectorSize, -1); + return false; +} + +bool sd_write_raw(uint32_t lba, const uint8_t* buffer) +{ + char path[32]; + std::snprintf(path, sizeof(path), "raw:%lu", static_cast(lba)); + const uint32_t start_ms = sd_io_begin("raw_write", path, kSdSectorSize); + bool result = false; + SdRuntimeBusGuard guard("sd_raw_write"); + if (!guard.locked()) + { + sd_io_end("raw_write", path, start_ms, false, kSdSectorSize, -2); + return false; + } + if (s_info.backend == SdCardBackend::SdFat) + { + result = s_block_device.writeSector(lba, buffer); + sd_io_end("raw_write", path, start_ms, result, kSdSectorSize); + return result; + } + sd_io_end("raw_write", path, start_ms, false, kSdSectorSize, -1); + return false; +} + +} // namespace platform::esp::arduino_common::storage + +namespace platform::esp::idf_common::sd_card_runtime +{ + +bool mount_sdmmc(sdmmc_host_runtime::SlotOwner owner, + const sdmmc_host_t& host, + const sdmmc_slot_config_t& slot_config, + const char* mount_point, + uint8_t max_files) +{ + namespace storage_detail = ::platform::esp::arduino_common::storage::detail; + storage_detail::SdRuntimeBusGuard guard("sd_mount"); + if (!guard.locked()) + { + return false; + } + return storage_detail::mount_sdmmc_locked( + owner, host, slot_config, mount_point, max_files); +} + +void unmount_sdmmc(sdmmc_host_runtime::SlotOwner owner) +{ + namespace storage_detail = ::platform::esp::arduino_common::storage::detail; + storage_detail::SdRuntimeBusGuard guard("sd_unmount_owner"); + if (!guard.locked()) + { + return; + } + if (storage_detail::s_owner == owner) + { + storage_detail::clear_mounted_locked(); + } +} + +sdmmc_card_t* mounted_card() +{ + return ::platform::esp::arduino_common::storage::detail::s_card; +} + +} // namespace platform::esp::idf_common::sd_card_runtime diff --git a/platform/esp/idf_common/src/sdmmc_host_runtime.cpp b/platform/esp/idf_common/src/sdmmc_host_runtime.cpp index 535a3a0a..04391975 100644 --- a/platform/esp/idf_common/src/sdmmc_host_runtime.cpp +++ b/platform/esp/idf_common/src/sdmmc_host_runtime.cpp @@ -133,81 +133,6 @@ const char* owner_name(SlotOwner owner) return "none"; } -esp_err_t mount_fatfs(SlotOwner owner, - const char* mount_point, - const sdmmc_host_t* host, - const sdmmc_slot_config_t* slot_config, - const esp_vfs_fat_mount_config_t* mount_config, - sdmmc_card_t** out_card) -{ - if (mount_point == nullptr || host == nullptr || slot_config == nullptr || - mount_config == nullptr || out_card == nullptr) - { - return ESP_ERR_INVALID_ARG; - } - - std::lock_guard lock(s_lifecycle_mutex); - const esp_err_t availability = ensure_slot_available_locked(owner, host->slot); - if (availability != ESP_OK) - { - return availability; - } - - const esp_err_t err = - esp_vfs_fat_sdmmc_mount(mount_point, host, slot_config, mount_config, out_card); - if (err != ESP_OK) - { - ESP_LOGW(kTag, - "SDMMC FATFS mount failed slot=%d owner=%s err=%s active_slots=%u host_refs=%u", - host->slot, - owner_name(owner), - esp_err_to_name(err), - static_cast(active_slot_count_locked()), - static_cast(s_host_ref_count)); - return err; - } - - record_acquired_slot_locked(owner, host->slot); - return ESP_OK; -} - -esp_err_t unmount_fatfs(SlotOwner owner, - const char* mount_point, - sdmmc_card_t* card) -{ - if (!valid_owner(owner) || mount_point == nullptr || card == nullptr) - { - return ESP_ERR_INVALID_ARG; - } - - const int slot = card->host.slot; - std::lock_guard lock(s_lifecycle_mutex); - if (!valid_slot(slot) || s_slot_owners[static_cast(slot)] != owner) - { - ESP_LOGW(kTag, - "SDMMC FATFS unmount denied slot=%d requester=%s owner=%s", - slot, - owner_name(owner), - valid_slot(slot) ? owner_name(s_slot_owners[static_cast(slot)]) - : "invalid-slot"); - return ESP_ERR_INVALID_STATE; - } - - const esp_err_t err = esp_vfs_fat_sdcard_unmount(mount_point, card); - if (err != ESP_OK) - { - ESP_LOGW(kTag, - "SDMMC FATFS unmount failed slot=%d owner=%s err=%s", - slot, - owner_name(owner), - esp_err_to_name(err)); - return err; - } - - record_released_slot_locked(owner, slot, esp_err_to_name(err)); - return ESP_OK; -} - esp_err_t initialize_slot(SlotOwner owner, const sdmmc_host_t& host, const sdmmc_slot_config_t& slot_config) diff --git a/platform/esp/idf_common/src/ui_common.cpp b/platform/esp/idf_common/src/ui_common.cpp index 82e8cea5..35ae6be8 100644 --- a/platform/esp/idf_common/src/ui_common.cpp +++ b/platform/esp/idf_common/src/ui_common.cpp @@ -4,6 +4,7 @@ #include #include +#include "platform/esp/arduino_common/storage/sd_card_runtime.h" #include "platform/esp/idf_common/bsp_runtime.h" #include "platform/ui/device_runtime.h" #include "platform/ui/settings_store.h" @@ -146,12 +147,11 @@ bool ui_take_screenshot_to_sd() } snprintf(path, sizeof(path), - "%s/screenshot_%s.bmp", - platform::esp::idf_common::bsp_runtime::sdcard_mount_point(), + "/screenshot_%s.bmp", ts); - FILE* file = fopen(path, "wb"); - if (!file) + platform::esp::arduino_common::storage::SdRuntimeFile file; + if (!file.open(path, "wb")) { lv_draw_buf_destroy(snap); return false; @@ -168,7 +168,7 @@ bool ui_take_screenshot_to_sd() static_cast((data_offset >> 8) & 0xFF), static_cast((data_offset >> 16) & 0xFF), static_cast((data_offset >> 24) & 0xFF)}; - fwrite(file_hdr, 1, sizeof(file_hdr), file); + file.write(file_hdr, sizeof(file_hdr)); uint8_t info_hdr[40] = {0}; info_hdr[0] = 40; @@ -186,7 +186,7 @@ bool ui_take_screenshot_to_sd() info_hdr[21] = static_cast((pixel_bytes >> 8) & 0xFF); info_hdr[22] = static_cast((pixel_bytes >> 16) & 0xFF); info_hdr[23] = static_cast((pixel_bytes >> 24) & 0xFF); - fwrite(info_hdr, 1, sizeof(info_hdr), file); + file.write(info_hdr, sizeof(info_hdr)); const uint8_t* pixels = static_cast(snap->data); std::vector rowbuf(row24, 0); @@ -207,11 +207,11 @@ bool ui_take_screenshot_to_sd() rowbuf[idx++] = g; rowbuf[idx++] = r; } - fwrite(rowbuf.data(), 1, rowbuf.size(), file); + file.write(rowbuf.data(), rowbuf.size()); } - fflush(file); - fclose(file); + file.flush(); + file.close(); lv_draw_buf_destroy(snap); return true; #else diff --git a/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp b/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp index 40fcc835..677fdccb 100644 --- a/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp +++ b/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp @@ -26,6 +26,7 @@ #include "freertos/task.h" #include "hi8561_driver.h" #include "lvgl.h" +#include "platform/esp/idf_common/app_runtime_support.h" #include "rm69a10_driver.h" #include "sdkconfig.h" #include "soc/soc_caps.h" @@ -78,6 +79,8 @@ constexpr uint32_t kKeyboardMonitorIntervalMs = 2000; constexpr uint32_t kKeyboardMonitorUiIntervalMs = 200; constexpr uint32_t kKeyboardMonitorTaskStackSize = 4096; constexpr UBaseType_t kKeyboardMonitorTaskPriority = 3; +constexpr uint32_t kAppLifecycleUiTimerIntervalMs = 10; +constexpr std::size_t kAppLifecycleUiEventsPerTick = 4; constexpr uint8_t kKeyboardAttachDebounceCount = 2; constexpr uint8_t kKeyboardDetachDebounceCount = 3; constexpr uint8_t kKeyboardI2cScanFirstAddress = 0x03; @@ -148,6 +151,7 @@ lv_display_t* s_display = nullptr; lv_indev_t* s_touch_indev = nullptr; lv_indev_t* s_keyboard_indev = nullptr; lv_timer_t* s_keyboard_monitor_ui_timer = nullptr; +lv_timer_t* s_app_lifecycle_ui_timer = nullptr; TaskHandle_t s_keyboard_monitor_task = nullptr; SemaphoreHandle_t s_keyboard_i2c_mutex = nullptr; i2c_master_dev_handle_t s_touch_i2c_handle = nullptr; @@ -1863,6 +1867,50 @@ void keyboard_monitor_ui_cb(lv_timer_t* timer) } } +void app_lifecycle_ui_timer_cb(lv_timer_t* timer) +{ + (void)timer; + platform::esp::idf_common::tickLvglTaskOwnedUiLifecycle( + kAppLifecycleUiEventsPerTick); +} + +bool start_app_lifecycle_ui_timer() +{ + if (s_app_lifecycle_ui_timer != nullptr) + { + return true; + } + if (!s_lvgl_ready || s_display == nullptr) + { + return false; + } + + if (!trail_mate_t_display_p4_display_lock(1000)) + { + ESP_LOGW(kTag, "Failed to start app lifecycle UI timer: LVGL lock timeout"); + return false; + } + + s_app_lifecycle_ui_timer = + lv_timer_create(app_lifecycle_ui_timer_cb, + kAppLifecycleUiTimerIntervalMs, + nullptr); + trail_mate_t_display_p4_display_unlock(); + + if (s_app_lifecycle_ui_timer == nullptr) + { + ESP_LOGW(kTag, "Failed to create app lifecycle UI timer"); + return false; + } + + platform::esp::idf_common::setLvglTaskOwnedUiDispatch(true); + ESP_LOGI(kTag, + "LVGL task-owned app UI dispatch enabled interval=%lums events=%u", + static_cast(kAppLifecycleUiTimerIntervalMs), + static_cast(kAppLifecycleUiEventsPerTick)); + return true; +} + void keyboard_module_monitor_task(void* arg) { (void)arg; @@ -2299,6 +2347,7 @@ extern "C" bool trail_mate_t_display_p4_display_runtime_init(void) } create_boot_screen(); + (void)start_app_lifecycle_ui_timer(); s_ready = true; ESP_LOGI(kTag, diff --git a/scripts/check_track_file_streaming.py b/scripts/check_track_file_streaming.py index 1abb6d1b..f8e55bab 100644 --- a/scripts/check_track_file_streaming.py +++ b/scripts/check_track_file_streaming.py @@ -34,7 +34,6 @@ TRACK_PATH_HINTS = ( "modules/ui_shared/include/ui/screens/gps/", "modules/ui_shared/include/ui/screens/tracker/", "platform/esp/arduino_common/src/platform_ui_route_storage.cpp", - "platform/esp/idf_common/src/platform_ui_route_storage.cpp", "platform/linux/common/src/platform/ui/route_storage.cpp", ) diff --git a/tests/reticulum_conformance/README.md b/tests/reticulum_conformance/README.md index 7de15562..2fd81cb3 100644 --- a/tests/reticulum_conformance/README.md +++ b/tests/reticulum_conformance/README.md @@ -18,7 +18,15 @@ The first conformance target is Trail Mate's Reticulum network-stack subset: - `platform/esp/arduino_common/.../lxmf_adapter.*` - `platform/esp/arduino_common/.../lxmf_runtime_state.h` - `platform/esp/arduino_common/.../lxmf_transport_runtime.*` +- `platform/esp/arduino_common/.../lxmf_destination_registry.*` +- `platform/esp/arduino_common/.../lxmf_path_manager.*` - `platform/esp/arduino_common/.../lxmf_link_runtime.*` +- `platform/esp/arduino_common/.../lxmf_link_manager.*` +- `platform/esp/arduino_common/.../lxmf_packet_router.*` +- `platform/esp/arduino_common/.../lxmf_ping_service.*` +- `platform/esp/arduino_common/.../lxmf_network_page_client.*` +- `platform/esp/arduino_common/.../lxmf_propagation_client.*` +- `platform/esp/arduino_common/.../lxmf_lxst_telephony_client.*` - `platform/esp/arduino_common/.../lxmf_resource_runtime.*` - `platform/esp/arduino_common/.../lxmf_propagation_runtime.*` - `platform/esp/arduino_common/.../lxmf_propagation_service_runtime.*` @@ -186,6 +194,13 @@ transport table helpers preserve the adapter's expected lookup, upsert, resolve, and cleanup behavior. It also verifies that the extracted link runtime preserves session lookup, close cleanup, lifecycle transition, culling, and removal rules without pulling product side effects into the runtime helper. The current +owner-manager slice verifies that destination registry, path manager, link +manager, packet router, ping service, pending network-page client, +propagation-client shell, and LXST telephony scratch owner remain explicit, +non-copyable owners rather than collapsing back into adapter-local vectors or +scratch fields. `AnnounceIngestor` is covered by the ESP build and scope guard +because its verification path depends on embedded announce signing/Serial +dependencies that do not belong in the parse-only native smoke. The current resource slice verifies incoming/outgoing transfer initialisation, hashmap window requests, hashmap updates, part receipt bookkeeping, split-resource assembly, proof completion, resource lookup/cancel helpers, and resource TTL diff --git a/tests/reticulum_conformance/check_scope_guard.py b/tests/reticulum_conformance/check_scope_guard.py index 5605bab1..b6942032 100644 --- a/tests/reticulum_conformance/check_scope_guard.py +++ b/tests/reticulum_conformance/check_scope_guard.py @@ -34,6 +34,51 @@ REQUIRED_FILES = [ Path("tests/reticulum_conformance/test_reticulum_announce_vectors.cpp"), Path("tests/reticulum_conformance/test_reticulum_supported_subset_vectors.cpp"), Path("tests/reticulum_conformance/test_reticulum_runtime_state_contract.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_destination_registry.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_destination_registry.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_path_manager.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_path_manager.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_link_manager.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_link_manager.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_packet_router.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_packet_router.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_announce_ingestor.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_announce_ingestor.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_ping_service.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_ping_service.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_network_page_client.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_network_page_client.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_propagation_client.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_propagation_client.cpp"), + Path( + "platform/esp/arduino_common/include/platform/esp/arduino_common/" + "chat/infra/lxmf/lxmf_lxst_telephony_client.h" + ), + Path("platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_lxst_telephony_client.cpp"), Path( "platform/esp/arduino_common/include/platform/esp/arduino_common/" "chat/infra/lxmf/lxmf_transport_runtime.h" @@ -363,6 +408,49 @@ def check_reticulum_interface_boundary() -> list[str]: return errors +def check_lxmf_adapter_runtime_owner_boundary() -> list[str]: + errors: list[str] = [] + header = ROOT / LXMF_ADAPTER_HEADER + if not header.is_file(): + return [f"missing LXMF adapter header: {LXMF_ADAPTER_HEADER.as_posix()}"] + + text = header.read_text(encoding="utf-8") + required_owner_fields = [ + "runtime::DestinationRegistry destination_registry_", + "runtime::PathManager path_manager_", + "runtime::LinkManager link_manager_", + "runtime::AnnounceIngestor announce_ingestor_", + "runtime::ReticulumPacketRouter packet_router_", + "runtime::PingService ping_service_", + "runtime::NetworkPageClient network_page_client_", + "runtime::PropagationClient propagation_client_", + "runtime::LxstTelephonyClient lxst_telephony_client_", + ] + for field in required_owner_fields: + if field not in text: + errors.append(f"LXMF adapter missing runtime owner field: {field}") + + forbidden_adapter_state = [ + "std::vector peers_", + "TransportRuntime transport_", + "LinkRuntime links_", + "std::vector pending_ping_requests_", + "std::vector pending_nomad_page_requests_", + "PropagationRuntime propagation_", + "PropagationStampRuntime propagation_stamp_", + "PeerInfo propagation_peer_scratch_", + "call_wire_scratch_", + ] + for term in forbidden_adapter_state: + if term in text: + errors.append( + "LXMF adapter must not directly own extracted runtime state: " + f"{term}" + ) + + return errors + + def check_fixture_metadata() -> list[str]: errors: list[str] = [] for relative in FIXTURE_FILES: @@ -447,6 +535,7 @@ def main() -> int: errors.extend(check_reticulum_group_source_boundary()) errors.extend(check_product_reticulum_config_accessor_boundary()) errors.extend(check_reticulum_interface_boundary()) + errors.extend(check_lxmf_adapter_runtime_owner_boundary()) if errors: for error in errors: diff --git a/tests/reticulum_conformance/test_reticulum_runtime_state_contract.cpp b/tests/reticulum_conformance/test_reticulum_runtime_state_contract.cpp index 265d1f32..b3887054 100644 --- a/tests/reticulum_conformance/test_reticulum_runtime_state_contract.cpp +++ b/tests/reticulum_conformance/test_reticulum_runtime_state_contract.cpp @@ -1,7 +1,15 @@ #include "chat/domain/reticulum_identity.h" #include "chat/infra/mesh_incoming_queue.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_delivery_runtime.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_destination_registry.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_manager.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_link_runtime.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_lxst_telephony_client.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_network_page_client.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_packet_router.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_path_manager.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_ping_service.h" +#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_client.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_runtime.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_service_runtime.h" #include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h" @@ -13,6 +21,8 @@ #include #include #include +#include +#include #include namespace @@ -77,6 +87,374 @@ int main() using namespace chat::lxmf::runtime; namespace reticulum = chat::reticulum; + static_assert(!std::is_copy_constructible::value, + "DestinationRegistry owns peer state and must not be copied"); + static_assert(!std::is_move_constructible::value, + "DestinationRegistry ownership must stay in place"); + static_assert(!std::is_copy_constructible::value, + "PathManager owns transport state and must not be copied"); + static_assert(!std::is_move_constructible::value, + "PathManager ownership must stay in place"); + static_assert(!std::is_copy_constructible::value, + "LinkManager owns link sessions and must not be copied"); + static_assert(!std::is_move_constructible::value, + "LinkManager ownership must stay in place"); + static_assert(!std::is_copy_constructible::value, + "PingService owns pending ping state and must not be copied"); + static_assert(!std::is_copy_constructible::value, + "NetworkPageClient owns pending page state and must not be copied"); + static_assert(!std::is_copy_constructible::value, + "PropagationClient owns propagation state and must not be copied"); + static_assert(!std::is_copy_constructible::value, + "LxstTelephonyClient owns call scratch state and must not be copied"); + static_assert( + std::is_same>::value, + "Resource payload list ownership must stay on PSRAM allocator"); + static_assert( + std::is_same>::value, + "Resource metadata buffers must stay on PSRAM allocator"); + static_assert( + std::is_same>::value, + "Resource bitmap buffers must stay on PSRAM allocator"); + static_assert( + std::is_same>::value, + "Resource map hash lists must stay on PSRAM allocator"); + static_assert( + std::is_same>::value, + "Propagation id lists must stay on PSRAM allocator"); + static_assert( + std::is_same>::value, + "Propagation message lists must stay on PSRAM allocator"); + + const auto registry_destination = + filled_hash(0x04); + const auto registry_identity = + filled_hash(0x24); + DestinationRegistry registry{}; + assert(registry.size() == 0); + PeerInfo& registry_peer = registry.upsertDestination(registry_destination.data()); + assert(registry.size() == 1); + assert(same_hash(registry_peer.destination_hash, registry_destination)); + copy_hash(registry_peer.identity_hash, registry_identity); + assert(registry.findByDestinationHash(registry_destination.data()) == ®istry_peer); + assert(registry.findByIdentityHash(registry_identity.data()) == ®istry_peer); + assert(registry.findByNodeId(registry_peer.node_id) == ®istry_peer); + assert(®istry.upsertDestination(registry_destination.data()) == ®istry_peer); + registry.clear(); + assert(registry.size() == 0); + + ReticulumPacketRouter router{}; + reticulum::ParsedPacket route_packet{}; + route_packet.packet_type = reticulum::PacketType::Announce; + assert(router.route(route_packet) == PacketRoute::Announce); + route_packet.packet_type = reticulum::PacketType::Proof; + assert(router.route(route_packet) == PacketRoute::Proof); + route_packet.packet_type = reticulum::PacketType::LinkRequest; + assert(router.route(route_packet) == PacketRoute::LinkRequest); + route_packet.packet_type = reticulum::PacketType::Data; + assert(router.route(route_packet) == PacketRoute::Data); + route_packet.packet_type = static_cast(0x7F); + assert(router.route(route_packet) == PacketRoute::LinkOrTransport); + + const auto manager_destination = + filled_hash(0x14); + const auto manager_packet_hash = filled_hash(0x34); + PathManager path_manager{}; + assert(!path_manager.isDuplicatePacket(manager_packet_hash.data())); + path_manager.rememberPacket(manager_packet_hash.data(), 100, 4); + assert(path_manager.isDuplicatePacket(manager_packet_hash.data())); + path_manager.forgetPacket(manager_packet_hash.data()); + assert(!path_manager.isDuplicatePacket(manager_packet_hash.data())); + PathEntry& managed_path = path_manager.upsertPath(manager_destination.data(), 4); + managed_path.hops = 2; + managed_path.updated_ms = 500; + assert(path_manager.findAnyPath(manager_destination.data()) == &managed_path); + assert(path_manager.findPath(manager_destination.data(), 600, 1000) == &managed_path); + assert(path_manager.findPath(manager_destination.data(), 1601, 1000) == nullptr); + path_manager.notePendingPathRequest(manager_destination.data(), 700, 4); + assert(path_manager.findPendingPathRequest(manager_destination.data()) != nullptr); + path_manager.resolvePendingPathRequest(manager_destination.data()); + assert(path_manager.findPendingPathRequest(manager_destination.data()) == nullptr); + + LinkManager link_manager{}; + LinkSession* managed_session = link_manager.appendSession(2); + assert(managed_session != nullptr); + copy_hash(managed_session->link_id, manager_destination); + copy_hash(managed_session->remote_destination_hash, manager_destination); + managed_session->destination = LocalDestinationKind::Delivery; + managed_session->state = LinkState::Active; + assert(link_manager.size() == 1); + assert(link_manager.findSession(manager_destination.data()) == managed_session); + assert(link_manager.findOpenSessionByDestination(manager_destination.data(), + LocalDestinationKind::Delivery) == + managed_session); + assert(link_manager.closeSession(*managed_session, LinkCloseReason::LocalClose, 900)); + assert(managed_session->state == LinkState::Closed); + link_manager.clear(); + assert(link_manager.size() == 0); + managed_session = link_manager.appendSession(2); + assert(managed_session != nullptr); + const auto resource_hash = filled_hash(0x44); + const auto resource_original_hash = + filled_hash(0x64); + const uint8_t resource_random[kResourceMapHashLen] = {0x01, 0x02, 0x03, 0x04}; + const uint8_t resource_request_id[] = {0x09, 0x0A}; + ResourceMetadataBuffer resource_hashmap; + resource_hashmap.insert(resource_hashmap.end(), + resource_random, + resource_random + sizeof(resource_random)); + LinkResourceTransfer* managed_incoming_resource = + link_manager.startIncomingResource(*managed_session, + resource_hash.data(), + resource_random, + resource_original_hash.data(), + resource_request_id, + sizeof(resource_request_id), + resource_hashmap.data(), + resource_hashmap.size(), + 8, + 8, + 1, + 1, + 1, + 0, + false, + false, + false, + false, + 1000, + 4); + assert(managed_incoming_resource != nullptr); + assert(link_manager.findIncomingResource(*managed_session, + resource_hash.data()) == + managed_incoming_resource); + const ResourceWindowRequest window_request = + link_manager.buildNextResourceWindowRequest(*managed_incoming_resource); + assert(window_request.valid); + link_manager.noteResourceWindowRequested(*managed_incoming_resource, false, 1005); + assert(managed_incoming_resource->last_activity_ms == 1005); + assert(link_manager.eraseIncomingResource(*managed_session, + resource_hash.data())); + LinkResourceTransfer managed_outgoing_resource{}; + assert(link_manager.initialiseOutgoingResource(managed_outgoing_resource, + resource_request_id, + sizeof(resource_request_id), + 8, + 8, + 1, + 0, + 1010, + 4)); + copy_hash(managed_outgoing_resource.resource_hash, resource_hash); + managed_outgoing_resource.message_id = 77; + assert(link_manager.appendOutgoingResource(*managed_session, + std::move(managed_outgoing_resource)) != + nullptr); + LinkResourceTransfer* queued_outgoing_resource = + link_manager.findOutgoingResource(*managed_session, resource_hash.data()); + assert(queued_outgoing_resource != nullptr); + bool saw_resource_message_id = false; + link_manager.takeTrackedOutgoingResourceMessageIds( + *managed_session, + [&saw_resource_message_id](uint32_t message_id) + { + saw_resource_message_id = message_id == 77; + }); + assert(saw_resource_message_id); + assert(queued_outgoing_resource->message_id == 0); + assert(link_manager.eraseOutgoingResource(*managed_session, + resource_hash.data())); + link_manager.clear(); + + PingService ping_service{}; + const uint8_t zero_destination[reticulum::kTruncatedHashSize] = {}; + assert(ping_service.queue(zero_destination, 100, 2) == + PendingPingQueueResult::Invalid); + assert(ping_service.queue(manager_destination.data(), 100, 2) == + PendingPingQueueResult::Queued); + assert(ping_service.queue(manager_destination.data(), 100, 2) == + PendingPingQueueResult::Duplicate); + bool ping_dispatched = false; + ping_service.pump( + 250, + false, + 1000, + 100, + 100, + [](const uint8_t*) + { return true; }, + [&ping_dispatched](const uint8_t*, uint32_t, uint32_t) + { + ping_dispatched = true; + return true; + }, + [](const uint8_t*, uint32_t) {}, + [](const PendingPingRequest&, uint32_t) + { assert(false); }); + assert(ping_dispatched); + assert(ping_service.size() == 0); + assert(ping_service.queue(manager_destination.data(), 100, 2) == + PendingPingQueueResult::Queued); + bool ping_timed_out = false; + ping_service.pump( + 500, + false, + 100, + 100, + 100, + [](const uint8_t*) + { return false; }, + [](const uint8_t*, uint32_t, uint32_t) + { return false; }, + [](const uint8_t*, uint32_t) {}, + [&ping_timed_out](const PendingPingRequest&, uint32_t) + { ping_timed_out = true; }); + assert(ping_timed_out); + assert(ping_service.size() == 0); + + NetworkPageClient page_client{}; + PendingNomadPageRequest* page_request = nullptr; + assert(page_client.empty()); + assert(page_client.queue(manager_destination.data(), + "/", + 100, + 1, + sizeof(PendingNomadPageRequest::path), + &page_request) == NetworkPageQueueResult::Queued); + assert(page_request != nullptr); + assert(page_client.size() == 1); + copy_hash(page_request->request_id, manager_destination); + assert(page_client.findByRequestId(manager_destination.data(), + manager_destination.data(), + reticulum::kTruncatedHashSize) == page_request); + assert(page_client.queue(manager_destination.data(), + "/", + 200, + 1, + sizeof(PendingNomadPageRequest::path), + &page_request) == NetworkPageQueueResult::Duplicate); + page_client.eraseAt(0); + assert(page_client.empty()); + + PropagationClient propagation_client{}; + assert(!propagation_client.state().has_active_node); + PropagationPeerState active_propagation_peer{}; + copy_hash(active_propagation_peer.propagation_hash, manager_destination); + active_propagation_peer.node_active = true; + active_propagation_peer.last_seen_s = 100; + propagation_client.state().peers.push_back(active_propagation_peer); + PropagationActivePeerSelection selected_peer = + propagation_client.selectActivePeer(false, + manager_destination.data(), + 100, + 30, + true); + assert(selected_peer.peer != nullptr); + assert(selected_peer.changed); + propagation_client.peerScratch().node_id = 0x12345678; + assert(propagation_client.state().has_active_node); + assert(same_hash(propagation_client.state().active_node_hash, manager_destination)); + PropagationActivePeerSelection selected_again = + propagation_client.selectActivePeer(false, + manager_destination.data(), + 101, + 30, + true); + assert(selected_again.peer != nullptr); + assert(!selected_again.changed); + propagation_client.clearActivePeer(); + assert(!propagation_client.state().has_active_node); + assert(all_zero(propagation_client.state().active_node_hash)); + assert(propagation_client.peerScratch().node_id == 0x12345678); + assert(propagation_client.startSyncIfDue(200, 1000, 60)); + assert(propagation_client.state().sync_stage == PropagationSyncStage::NeedList); + uint8_t sync_request_id[reticulum::kTruncatedHashSize] = {}; + copy_hash(sync_request_id, manager_destination); + LinkPendingRequest sync_request{}; + sync_request.request_id.assign(sync_request_id, + sync_request_id + sizeof(sync_request_id)); + assert(!propagation_client.syncRequestMatches(sync_request)); + propagation_client.markSyncRequestSent(sync_request_id, + PropagationSyncStage::Listing); + assert(propagation_client.syncRequestMatches(sync_request)); + PropagationIdList remote_ids; + RuntimeByteBuffer known_id(reticulum::kFullHashSize, 0x11); + RuntimeByteBuffer wanted_id(reticulum::kFullHashSize, 0x22); + propagation_client.rememberDeliveredTransient(known_id.data(), 200, 4); + remote_ids.push_back(known_id); + remote_ids.push_back(wanted_id); + propagation_client.noteListingResult(remote_ids, 1); + assert(propagation_client.state().sync_stage == + PropagationSyncStage::NeedMessages); + assert(propagation_client.state().sync_haves.size() == 1); + assert(propagation_client.state().sync_wants.size() == 1); + propagation_client.noteDownloadResult(false, 1200); + assert(propagation_client.state().sync_stage == + PropagationSyncStage::NeedAcknowledge); + propagation_client.markAcknowledged(); + assert(propagation_client.state().sync_stage == PropagationSyncStage::Complete); + assert(propagation_client.syncHaveCount() == 1); + propagation_client.finishSyncComplete(220); + assert(propagation_client.state().sync_stage == PropagationSyncStage::Idle); + assert(!propagation_client.state().initial_sync_pending); + PendingPropagationUpload upload_a{}; + upload_a.message_id = 101; + upload_a.created_ms = 1000; + upload_a.state = PropagationUploadState::WaitingNode; + PendingPropagationUpload* queued_upload = + propagation_client.queueUpload(std::move(upload_a), 2); + assert(queued_upload != nullptr); + assert(propagation_client.hasPendingUploads()); + assert(propagation_client.firstPendingUpload() == queued_upload); + assert(propagation_client.firstPendingUpload()->message_id == 101); + + PendingPropagationUpload upload_b{}; + upload_b.message_id = 102; + upload_b.created_ms = 1005; + upload_b.state = PropagationUploadState::WaitingNode; + assert(propagation_client.queueUpload(std::move(upload_b), 2) != nullptr); + PendingPropagationUpload upload_c{}; + upload_c.message_id = 103; + upload_c.created_ms = 1010; + upload_c.state = PropagationUploadState::WaitingNode; + assert(propagation_client.queueUpload(std::move(upload_c), 2) == nullptr); + + propagation_client.markExpiredUploads(1101, 100); + std::vector failed_uploads = + propagation_client.takeFailedUploads(); + assert(failed_uploads.size() == 1); + assert(failed_uploads[0].message_id == 101); + assert(propagation_client.hasPendingUploads()); + assert(propagation_client.firstPendingUpload()->message_id == 102); + assert(propagation_client.removeFirstPendingUpload()); + assert(!propagation_client.removeFirstPendingUpload()); + assert(!propagation_client.hasPendingUploads()); + + PendingPropagationUpload upload_d{}; + upload_d.message_id = 104; + upload_d.state = PropagationUploadState::NeedsStamp; + assert(propagation_client.queueUpload(std::move(upload_d), 2) != nullptr); + std::vector all_uploads = + propagation_client.takeAllPendingUploads(); + assert(all_uploads.size() == 1); + assert(all_uploads[0].message_id == 104); + assert(!propagation_client.hasPendingUploads()); +#if !defined(TRAIL_MATE_RETICULUM_PARSE_ONLY) + propagation_client.stamp().reset(); +#endif + + LxstTelephonyClient telephony_client{}; + assert(telephony_client.scratch() != nullptr); + assert(telephony_client.scratchCapacity() == reticulum::kReticulumMtu); + telephony_client.scratch()[0] = 0xA5; + assert(telephony_client.scratch()[0] == 0xA5); + TransportRuntime transport{}; assert(transport.paths.empty()); assert(transport.packet_filter.empty()); @@ -416,17 +794,20 @@ int main() const std::array map_hash_0{0xA0, 0xA1, 0xA2, 0xA3}; const std::array map_hash_1{0xB0, 0xB1, 0xB2, 0xB3}; const std::array map_hash_2{0xC0, 0xC1, 0xC2, 0xC3}; - std::vector first_hashmap; + ResourceMetadataBuffer first_hashmap; first_hashmap.insert(first_hashmap.end(), map_hash_0.begin(), map_hash_0.end()); first_hashmap.insert(first_hashmap.end(), map_hash_1.begin(), map_hash_1.end()); + const uint8_t incoming_request_id[] = {0x31, 0x32}; LinkResourceTransfer incoming_resource{}; assert(initialiseIncomingResourceTransfer(incoming_resource, packet_hash.data(), random_hash.data(), original_hash.data(), - {0x31, 0x32}, - std::move(first_hashmap), + incoming_request_id, + sizeof(incoming_request_id), + first_hashmap.data(), + first_hashmap.size(), 6, 9, 3, @@ -486,10 +867,11 @@ int main() noteResourceWindowRequest(incoming_resource, window.needs_more_hashmap, 530); assert(incoming_resource.waiting_for_hashmap); - std::vector second_hashmap(map_hash_2.begin(), map_hash_2.end()); + ResourceMetadataBuffer second_hashmap(map_hash_2.begin(), map_hash_2.end()); assert(applyResourceHashmapUpdate(incoming_resource, 1, - second_hashmap, + second_hashmap.data(), + second_hashmap.size(), 2, 540)); assert(!incoming_resource.waiting_for_hashmap); @@ -543,13 +925,16 @@ int main() assert(!eraseLinkResourceByHash(resource_session.incoming_resources, packet_hash.data())); LinkResourceTransfer split_segment_1{}; + const uint8_t split_request_id[] = {0x44}; + ResourceMetadataBuffer split_hashmap(map_hash_0.begin(), map_hash_0.end()); assert(initialiseIncomingResourceTransfer(split_segment_1, packet_hash.data(), random_hash.data(), original_hash.data(), - {0x44}, - std::vector(map_hash_0.begin(), - map_hash_0.end()), + split_request_id, + sizeof(split_request_id), + split_hashmap.data(), + split_hashmap.size(), 3, 3, 1, @@ -669,11 +1054,18 @@ int main() assert(findPropagationEntry(propagation, transient_a.data()) != nullptr); assert(findPropagationEntry(propagation, transient_b.data()) == nullptr); - std::vector> offer_ids; - offer_ids.emplace_back(transient_a.begin(), transient_a.end()); - offer_ids.emplace_back(transient_b.begin(), transient_b.end()); - offer_ids.push_back({0x01, 0x02}); - std::vector> missing_ids = + auto append_runtime_id = [](PropagationIdList& out, const auto& id) + { + ResourcePayloadBuffer item; + item.assign(id.begin(), id.end()); + out.push_back(std::move(item)); + }; + + PropagationIdList offer_ids; + append_runtime_id(offer_ids, transient_a); + append_runtime_id(offer_ids, transient_b); + offer_ids.push_back(ResourcePayloadBuffer{0x01, 0x02}); + PropagationIdList missing_ids = collectMissingPropagationTransientIds(propagation, offer_ids); assert(missing_ids.size() == 1); assert(missing_ids.front().size() == transient_b.size()); @@ -705,13 +1097,13 @@ int main() sizeof(propagated_b), 930, propagation_limits.max_entries)); - std::vector> destination_ids = + PropagationIdList destination_ids = collectPropagationEntryIdsForDestination(propagation, delivery_hash.data()); assert(destination_ids.size() == 2); - std::vector> want_ids; - want_ids.emplace_back(transient_a.begin(), transient_a.end()); - want_ids.emplace_back(transient_b.begin(), transient_b.end()); + PropagationIdList want_ids; + append_runtime_id(want_ids, transient_a); + append_runtime_id(want_ids, transient_b); PropagationMessageSelection selection = collectPropagationMessagesForWants(propagation, want_ids, diff --git a/third_party/sdfat/.piopm b/third_party/sdfat/.piopm new file mode 100644 index 00000000..48ef8f4d --- /dev/null +++ b/third_party/sdfat/.piopm @@ -0,0 +1 @@ +{"type": "library", "name": "SdFat", "version": "2.3.1", "spec": {"owner": "greiman", "id": 322, "name": "SdFat", "requirements": null, "uri": null}} \ No newline at end of file diff --git a/third_party/sdfat/LICENSE.md b/third_party/sdfat/LICENSE.md new file mode 100644 index 00000000..a8147353 --- /dev/null +++ b/third_party/sdfat/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2011..2020 Bill Greiman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/third_party/sdfat/library.properties b/third_party/sdfat/library.properties new file mode 100644 index 00000000..07eb5415 --- /dev/null +++ b/third_party/sdfat/library.properties @@ -0,0 +1,11 @@ +name=SdFat +version=2.3.1 +license=MIT +author=Bill Greiman +maintainer=Bill Greiman +sentence=Provides access to SD memory cards. +paragraph=The SdFat library supports FAT16, FAT32, and exFAT file systems on Standard SD, SDHC, and SDXC cards. +category=Data Storage +url=https://github.com/greiman/SdFat +repository=https://github.com/greiman/SdFat.git +architectures=* diff --git a/third_party/sdfat/src/.clang-format-ignore b/third_party/sdfat/src/.clang-format-ignore new file mode 100644 index 00000000..8766e866 --- /dev/null +++ b/third_party/sdfat/src/.clang-format-ignore @@ -0,0 +1 @@ +SdCard/PioSdio/PioSdioCard.pio.h diff --git a/third_party/sdfat/src/BufferedPrint.h b/third_party/sdfat/src/BufferedPrint.h new file mode 100644 index 00000000..b8c3db51 --- /dev/null +++ b/third_party/sdfat/src/BufferedPrint.h @@ -0,0 +1,268 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief Fast buffered print. + */ +#ifdef __AVR__ +#include +#endif // __AVR__ +#include "common/FmtNumber.h" +/** + * \class BufferedPrint + * \brief Fast buffered print template. + */ +template +class BufferedPrint { + public: + BufferedPrint() : m_wr(nullptr), m_in(0) {} + /** BufferedPrint constructor. + * \param[in] wr Print destination. + */ + explicit BufferedPrint(WriteClass* wr) : m_wr(wr), m_in(0) {} + /** Initialize the BuffedPrint class. + * \param[in] wr Print destination. + */ + void begin(WriteClass* wr) { + m_wr = wr; + m_in = 0; + } + /** Flush the buffer - same as sync() with no status return. */ + void flush() { sync(); } + /** Print a character followed by a field terminator. + * \param[in] c character to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + size_t printField(char c, char term) { + char buf[3]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + *--str = c; + return write(str, buf + sizeof(buf) - str); + } + /** Print a string stored in AVR flash followed by a field terminator. + * \param[in] fsh string to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + size_t printField(const __FlashStringHelper* fsh, char term) { +#ifdef __AVR__ + size_t rtn = 0; + PGM_P p = reinterpret_cast(fsh); + char c; + while ((c = pgm_read_byte(p++))) { + if (!write(&c, 1)) { + return 0; + } + rtn++; + } + if (term) { + char buf[2]; + char* str = buf + sizeof(buf); + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + rtn += write(str, buf + sizeof(buf) - str); + } + return rtn; +#else // __AVR__ + return printField(reinterpret_cast(fsh), term); +#endif // __AVR__ + } + /** Print a string followed by a field terminator. + * \param[in] str string to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + size_t printField(const char* str, char term) { + size_t rtn = write(str, strlen(str)); + if (term) { + char buf[2]; + char* ptr = buf + sizeof(buf); + *--ptr = term; + if (term == '\n') { + *--ptr = '\r'; + } + rtn += write(ptr, buf + sizeof(buf) - ptr); + } + return rtn; + } + /** Print a double followed by a field terminator. + * \param[in] d The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t printField(double d, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, d, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a float followed by a field terminator. + * \param[in] f The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t printField(float f, char term, uint8_t prec = 2) { + return printField(static_cast(f), term, prec); + } + /** Print an integer value for 8, 16, and 32 bit signed and unsigned types. + * \param[in] n The value to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + template + size_t printField(Type n, char term) { + const uint8_t DIM = sizeof(Type) <= 2 ? 8 : 13; + char buf[DIM]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + Type p = n < 0 ? -n : n; + if (sizeof(Type) <= 2) { + str = fmtBase10(str, static_cast(p)); + } else { + str = fmtBase10(str, static_cast(p)); + } + if (n < 0) { + *--str = '-'; + } + return write(str, buf + sizeof(buf) - str); + } + /** Print CR LF. + * \return true for success or false if an error occurs. + */ + size_t println() { + char buf[2]; + buf[0] = '\r'; + buf[1] = '\n'; + return write(buf, 2); + } + /** Print a double. + * \param[in] d The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t print(double d, uint8_t prec = 2) { return printField(d, 0, prec); } + /** Print a double followed by CR LF. + * \param[in] d The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t println(double d, uint8_t prec = 2) { + return printField(d, '\n', prec); + } + /** Print a float. + * \param[in] f The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t print(float f, uint8_t prec = 2) { + return printField(static_cast(f), 0, prec); + } + /** Print a float followed by CR LF. + * \param[in] f The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t println(float f, uint8_t prec) { + return printField(static_cast(f), '\n', prec); + } + /** Print character, string, or number. + * \param[in] v item to print. + * \return true for success or false if an error occurs. + */ + template + size_t print(Type v) { + return printField(v, 0); + } + /** Print character, string, or number followed by CR LF. + * \param[in] v item to print. + * \return true for success or false if an error occurs. + */ + template + size_t println(Type v) { + return printField(v, '\n'); + } + + /** Flush the buffer. + * \return true for success or false if an error occurs. + */ + bool sync() { + if (!m_wr || m_wr->write(m_buf, m_in) != m_in) { + return false; + } + m_in = 0; + return true; + } + /** Write data to an open file. + * \param[in] src Pointer to the location of the data to be written. + * + * \param[in] n Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a n. + */ + size_t write(const void* src, size_t n) { + if ((m_in + n) > sizeof(m_buf)) { + if (!sync()) { + return 0; + } + if (n >= sizeof(m_buf)) { + return n == m_wr->write((const uint8_t*)src, n) ? n : 0; + } + } + memcpy(m_buf + m_in, src, n); + m_in += n; + return n; + } + + private: + WriteClass* m_wr; + uint8_t m_in; + // Insure room for double. + uint8_t m_buf[BUF_DIM < 24 ? 24 : BUF_DIM]; // NOLINT +}; diff --git a/third_party/sdfat/src/CPPLINT.cfg b/third_party/sdfat/src/CPPLINT.cfg new file mode 100644 index 00000000..b5136de4 --- /dev/null +++ b/third_party/sdfat/src/CPPLINT.cfg @@ -0,0 +1,3 @@ +filter=-build/include,-runtime/references,-build/header_guard +filter=-whitespace/indent_namespace +exclude_files=SdFatDebugConfig.h \ No newline at end of file diff --git a/third_party/sdfat/src/ExFatLib/ExFatDbg.cpp b/third_party/sdfat/src/ExFatLib/ExFatDbg.cpp new file mode 100644 index 00000000..8b0f330e --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatDbg.cpp @@ -0,0 +1,620 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "../common/upcase.h" +#include "ExFatLib.h" +#include "ExFatVolume.h" +#ifndef DOXYGEN_SHOULD_SKIP_THIS +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t h); +static void printHex(print_t* pr, uint16_t val); +static void printHex(print_t* pr, uint32_t val); +static void printHex64(print_t* pr, uint64_t n); +static void println64(print_t* pr, uint64_t n); +//------------------------------------------------------------------------------ +static void dmpDirData(print_t* pr, const DirGeneric_t* dir) { + for (uint8_t k = 0; k < 31; k++) { + if (k) { + pr->write(' '); + } + printHex(pr, dir->data[k]); + } + pr->println(); +} +//------------------------------------------------------------------------------ +static uint16_t exFatDirChecksum(const void* dir, uint16_t checksum) { + const uint8_t* data = reinterpret_cast(dir); + bool skip = data[0] == EXFAT_TYPE_FILE; + for (size_t i = 0; i < 32; i += (i == 1 && skip ? 3 : 1)) { + checksum = ((checksum << 15) | (checksum >> 1)) + data[i]; + } + return checksum; +} + +//------------------------------------------------------------------------------ +static uint16_t hashDir(const DirName_t* dir, uint16_t hash) { + for (uint8_t i = 0; i < 30; i += 2) { + uint16_t u = getLe16(dir->unicode + i); + if (!u) { + break; + } + uint16_t c = toUpcase(u); + hash = ((hash << 15) | (hash >> 1)) + (c & 0XFF); + hash = ((hash << 15) | (hash >> 1)) + (c >> 8); + } + return hash; +} +//------------------------------------------------------------------------------ +static void printDateTime(print_t* pr, uint32_t timeDate, uint8_t ms, + int8_t tz) { + fsPrintDateTime(pr, timeDate, ms, tz); + pr->println(); +} +//------------------------------------------------------------------------------ +static void printDirBitmap(print_t* pr, const DirBitmap_t* dir) { + pr->print(F("dirBitmap: 0x")); + pr->println(dir->type, HEX); + pr->print(F("flags: 0x")); + pr->println(dir->flags, HEX); + pr->print(F("firstCluster: ")); + pr->println(getLe32(dir->firstCluster)); + pr->print(F("size: ")); + println64(pr, getLe64(dir->size)); +} +//------------------------------------------------------------------------------ +static void printDirFile(print_t* pr, const DirFile_t* dir) { + pr->print(F("dirFile: 0x")); + pr->println(dir->type, HEX); + pr->print(F("setCount: ")); + pr->println(dir->setCount); + pr->print(F("setChecksum: 0x")); + pr->println(getLe16(dir->setChecksum), HEX); + pr->print(F("attributes: 0x")); + pr->println(getLe16(dir->attributes), HEX); + pr->print(F("createTime: ")); + printDateTime(pr, getLe32(dir->createTime), dir->createTimeMs, + dir->createTimezone); + pr->print(F("modifyTime: ")); + printDateTime(pr, getLe32(dir->modifyTime), dir->modifyTimeMs, + dir->modifyTimezone); + pr->print(F("accessTime: ")); + printDateTime(pr, getLe32(dir->accessTime), 0, dir->accessTimezone); +} +//------------------------------------------------------------------------------ +static void printDirLabel(print_t* pr, const DirLabel_t* dir) { + pr->print(F("dirLabel: 0x")); + pr->println(dir->type, HEX); + pr->print(F("labelLength: ")); + pr->println(dir->labelLength); + pr->print(F("unicode: ")); + for (size_t i = 0; i < dir->labelLength; i++) { + pr->write(dir->unicode[2 * i]); + } + pr->println(); +} +//------------------------------------------------------------------------------ +static void printDirName(print_t* pr, const DirName_t* dir) { + pr->print(F("dirName: 0x")); + pr->println(dir->type, HEX); + pr->print(F("unicode: ")); + for (size_t i = 0; i < 30; i += 2) { + uint16_t c = getLe16(dir->unicode + i); + if (c == 0) break; + if (c < 128) { + pr->print(static_cast(c)); + } else { + pr->print("0x"); + pr->print(c, HEX); + } + pr->print(' '); + } + pr->println(); +} +//------------------------------------------------------------------------------ +static void printDirStream(print_t* pr, const DirStream_t* dir) { + pr->print(F("dirStream: 0x")); + pr->println(dir->type, HEX); + pr->print(F("flags: 0x")); + pr->println(dir->flags, HEX); + pr->print(F("nameLength: ")); + pr->println(dir->nameLength); + pr->print(F("nameHash: 0x")); + pr->println(getLe16(dir->nameHash), HEX); + pr->print(F("validLength: ")); + println64(pr, getLe64(dir->validLength)); + pr->print(F("firstCluster: ")); + pr->println(getLe32(dir->firstCluster)); + pr->print(F("dataLength: ")); + println64(pr, getLe64(dir->dataLength)); +} +//------------------------------------------------------------------------------ +static void printDirUpcase(print_t* pr, const DirUpcase_t* dir) { + pr->print(F("dirUpcase: 0x")); + pr->println(dir->type, HEX); + pr->print(F("checksum: 0x")); + pr->println(getLe32(dir->checksum), HEX); + pr->print(F("firstCluster: ")); + pr->println(getLe32(dir->firstCluster)); + pr->print(F("size: ")); + println64(pr, getLe64(dir->size)); +} +//------------------------------------------------------------------------------ +static void printExFatBoot(print_t* pr, pbs_t* pbs) { + const BpbExFat_t* ebs = reinterpret_cast(pbs->bpb); + pr->print(F("bpbSig: 0x")); + pr->println(getLe16(pbs->signature), HEX); + pr->print(F("FileSystemName: ")); + pr->write(reinterpret_cast(pbs->oemName), 8); + pr->println(); + for (size_t i = 0; i < sizeof(ebs->mustBeZero); i++) { + if (ebs->mustBeZero[i]) { + pr->println(F("mustBeZero error")); + break; + } + } + pr->print(F("PartitionOffset: 0x")); + printHex64(pr, getLe64(ebs->partitionOffset)); + pr->print(F("VolumeLength: ")); + println64(pr, getLe64(ebs->volumeLength)); + pr->print(F("FatOffset: 0x")); + pr->println(getLe32(ebs->fatOffset), HEX); + pr->print(F("FatLength: ")); + pr->println(getLe32(ebs->fatLength)); + pr->print(F("ClusterHeapOffset: 0x")); + pr->println(getLe32(ebs->clusterHeapOffset), HEX); + pr->print(F("ClusterCount: ")); + pr->println(getLe32(ebs->clusterCount)); + pr->print(F("RootDirectoryCluster: ")); + pr->println(getLe32(ebs->rootDirectoryCluster)); + pr->print(F("VolumeSerialNumber: 0x")); + pr->println(getLe32(ebs->volumeSerialNumber), HEX); + pr->print(F("FileSystemRevision: 0x")); + pr->println(getLe32(ebs->fileSystemRevision), HEX); + pr->print(F("VolumeFlags: 0x")); + pr->println(getLe16(ebs->volumeFlags), HEX); + pr->print(F("BytesPerSectorShift: ")); + pr->println(ebs->bytesPerSectorShift); + pr->print(F("SectorsPerClusterShift: ")); + pr->println(ebs->sectorsPerClusterShift); + pr->print(F("NumberOfFats: ")); + pr->println(ebs->numberOfFats); + pr->print(F("DriveSelect: 0x")); + pr->println(ebs->driveSelect, HEX); + pr->print(F("PercentInUse: ")); + pr->println(ebs->percentInUse); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t h) { + if (h < 16) { + pr->write('0'); + } + pr->print(h, HEX); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint16_t val) { + bool space = true; + for (uint8_t i = 0; i < 4; i++) { + uint8_t h = (val >> (12 - 4 * i)) & 15; + if (h || i == 3) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint32_t val) { + bool space = true; + for (uint8_t i = 0; i < 8; i++) { + uint8_t h = (val >> (28 - 4 * i)) & 15; + if (h || i == 7) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +static void printHex64(print_t* pr, uint64_t n) { + char buf[17]; + char* str = &buf[sizeof(buf) - 1]; + *str = '\0'; + do { + uint8_t h = n & 15; + *--str = h < 10 ? h + '0' : h + 'A' - 10; + n >>= 4; + } while (n); + pr->println(str); +} +//------------------------------------------------------------------------------ +static void println64(print_t* pr, uint64_t n) { + char buf[21]; + char* str = &buf[sizeof(buf) - 1]; + *str = '\0'; + do { + uint64_t m = n; + n /= 10; + *--str = m - 10 * n + '0'; + } while (n); + pr->println(str); +} +//------------------------------------------------------------------------------ +static void printMbr(print_t* pr, const MbrSector_t* mbr) { + pr->print(F("mbrSig: 0x")); + pr->println(getLe16(mbr->signature), HEX); + for (int i = 0; i < 4; i++) { + printHex(pr, mbr->part[i].boot); + pr->write(' '); + for (int k = 0; k < 3; k++) { + printHex(pr, mbr->part[i].beginCHS[k]); + pr->write(' '); + } + printHex(pr, mbr->part[i].type); + pr->write(' '); + for (int k = 0; k < 3; k++) { + printHex(pr, mbr->part[i].endCHS[k]); + pr->write(' '); + } + pr->print(getLe32(mbr->part[i].startSector), HEX); + pr->print(' '); + pr->println(getLe32(mbr->part[i].totalSectors), HEX); + } +} +//============================================================================== +void ExFatPartition::checkUpcase(print_t* pr) { + bool skip = false; + uint16_t u = 0; + uint8_t* upcase = nullptr; + uint32_t size = 0; + Sector_t sector = clusterStartSector(m_rootDirectoryCluster); + uint8_t* cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("read root failed")); + return; + } + const DirUpcase_t* dir = reinterpret_cast(cache); + + pr->println(F("\nChecking upcase table")); + for (size_t i = 0; i < 16; i++) { + if (dir[i].type == EXFAT_TYPE_UPCASE) { + sector = clusterStartSector(getLe32(dir[i].firstCluster)); + size = getLe64(dir[i].size); + break; + } + } + if (!size) { + pr->println(F("upcase not found")); + return; + } + for (size_t i = 0; i < size / 2; i++) { + if ((i % 256) == 0) { + upcase = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ); + if (!upcase) { + pr->println(F("read upcase failed")); + return; + } + } + uint16_t v = getLe16(&upcase[2 * (i & 0XFF)]); + if (skip) { + pr->print("skip "); + pr->print(u); + pr->write(' '); + pr->println(v); + } + if (v == 0XFFFF) { + skip = true; + } else if (skip) { + for (uint16_t k = 0; k < v; k++) { + uint16_t x = toUpcase(u + k); + if (x != (u + k)) { + printHex(pr, static_cast(u + k)); + pr->write(','); + printHex(pr, x); + pr->println("<<<<<<<<<<<<<<<<<<<<"); + } + } + u += v; + skip = false; + } else { + uint16_t x = toUpcase(u); + if (v != x) { + printHex(pr, u); + pr->write(','); + printHex(pr, x); + pr->write(','); + printHex(pr, v); + pr->println(); + } + u++; + } + } + pr->println(F("Done checkUpcase")); +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpBitmap(print_t* pr) { + pr->println(F("bitmap:")); + dmpSector(pr, m_clusterHeapStartSector); +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpCluster(print_t* pr, Cluster_t cluster, uint32_t offset, + uint32_t count) { + Sector_t sector = clusterStartSector(cluster) + offset; + for (uint32_t i = 0; i < count; i++) { + pr->print(F("\nSector: ")); + pr->println(sector + i, HEX); + dmpSector(pr, sector + i); + } +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) { + Sector_t sector = m_fatStartSector + start; + Cluster_t cluster = 128 * start; + pr->println(F("FAT:")); + for (uint32_t i = 0; i < count; i++) { + uint8_t* cache = dataCachePrepare(sector + i, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("cache read failed")); + return; + } + const uint32_t* fat = reinterpret_cast(cache); + for (size_t k = 0; k < 128; k++) { + if (0 == cluster % 8) { + if (k) { + pr->println(); + } + printHex(pr, cluster); + } + cluster++; + pr->write(' '); + printHex(pr, fat[k]); + } + pr->println(); + } +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpSector(print_t* pr, Sector_t sector, uint8_t w) { + const uint8_t* cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("dmpSector failed")); + return; + } + for (uint16_t i = 0; i < m_bytesPerSector; i++) { + if (i % w == 0) { + if (i) { + pr->println(); + } + printHex(pr, i); + } + pr->write(' '); + printHex(pr, cache[i]); + } + pr->println(); +} +//------------------------------------------------------------------------------ +bool ExFatPartition::printDir(print_t* pr, ExFatFile* file) { + DirGeneric_t* dir = nullptr; + const DirFile_t* dirFile; + const DirStream_t* dirStream; + const DirName_t* dirName; + uint16_t calcHash = 0; + uint16_t nameHash = 0; + uint16_t setChecksum = 0; + uint16_t calcChecksum = 0; + uint8_t nameLength = 0; + uint8_t setCount = 0; + uint8_t nUnicode; + +#define RAW_ROOT +#ifndef RAW_ROOT + while (1) { + uint8_t buf[FS_DIR_SIZE]; + if (file->read(buf, FS_DIR_SIZE) != FS_DIR_SIZE) { + break; + } + dir = reinterpret_cast(buf); +#else // RAW_ROOT + (void)file; + uint32_t nDir = 1UL << (m_sectorsPerClusterShift + 4); + Sector_t sector = clusterStartSector(m_rootDirectoryCluster); + for (uint32_t iDir = 0; iDir < nDir; iDir++) { + size_t i = iDir % 16; + if (i == 0) { + uint8_t* cache = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ); + if (!cache) { + return false; + } + dir = reinterpret_cast(cache); + } else { + dir++; + } +#endif // RAW_ROOT + if (dir->type == EXFAT_TYPE_END_DIR) { + break; + } + pr->println(); + + switch (dir->type) { + case EXFAT_TYPE_BITMAP: + printDirBitmap(pr, reinterpret_cast(dir)); + break; + + case EXFAT_TYPE_UPCASE: + printDirUpcase(pr, reinterpret_cast(dir)); + break; + + case EXFAT_TYPE_LABEL: + printDirLabel(pr, reinterpret_cast(dir)); + break; + + case EXFAT_TYPE_FILE: + dirFile = reinterpret_cast(dir); + printDirFile(pr, dirFile); + setCount = dirFile->setCount; + setChecksum = getLe16(dirFile->setChecksum); + calcChecksum = exFatDirChecksum(dir, 0); + break; + + case EXFAT_TYPE_STREAM: + dirStream = reinterpret_cast(dir); + printDirStream(pr, dirStream); + nameLength = dirStream->nameLength; + nameHash = getLe16(dirStream->nameHash); + calcChecksum = exFatDirChecksum(dir, calcChecksum); + setCount--; + calcHash = 0; + break; + + case EXFAT_TYPE_NAME: + dirName = reinterpret_cast(dir); + printDirName(pr, dirName); + calcChecksum = exFatDirChecksum(dir, calcChecksum); + nUnicode = nameLength > 15 ? 15 : nameLength; + calcHash = hashDir(dirName, calcHash); + nameLength -= nUnicode; + setCount--; + if (nameLength == 0 || setCount == 0) { + pr->print(F("setChecksum: 0x")); + pr->print(setChecksum, HEX); + if (setChecksum != calcChecksum) { + pr->print(F(" != calcChecksum: 0x")); + } else { + pr->print(F(" == calcChecksum: 0x")); + } + pr->println(calcChecksum, HEX); + pr->print(F("nameHash: 0x")); + pr->print(nameHash, HEX); + if (nameHash != calcHash) { + pr->print(F(" != calcHash: 0x")); + } else { + pr->print(F(" == calcHash: 0x")); + } + pr->println(calcHash, HEX); + } + break; + + default: + if (dir->type & EXFAT_TYPE_USED) { + pr->print(F("Unknown dirType: 0x")); + } else { + pr->print(F("Unused dirType: 0x")); + } + pr->println(dir->type, HEX); + dmpDirData(pr, dir); + break; + } + } + pr->println(F("Done")); + return true; +} +//------------------------------------------------------------------------------ +void ExFatPartition::printFat(print_t* pr) { + Cluster_t next; + pr->println(F("FAT:")); + for (Cluster_t cluster = 0; cluster < 16; cluster++) { + int8_t status = fatGet(cluster, &next); + pr->print(cluster, HEX); + pr->write(' '); + if (status == 0) { + next = EXFAT_EOC; + } + pr->println(next, HEX); + } +} +//------------------------------------------------------------------------------ +void ExFatPartition::printUpcase(print_t* pr) { + uint8_t* upcase = nullptr; + Sector_t sector; + uint32_t size = 0; + uint32_t checksum = 0; + const DirUpcase_t* dir; + sector = clusterStartSector(m_rootDirectoryCluster); + upcase = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + dir = reinterpret_cast(upcase); + if (!dir) { + pr->println(F("read root dir failed")); + return; + } + for (size_t i = 0; i < 16; i++) { + if (dir[i].type == EXFAT_TYPE_UPCASE) { + sector = clusterStartSector(getLe32(dir[i].firstCluster)); + size = getLe64(dir[i].size); + break; + } + } + if (!size) { + pr->println(F("upcase not found")); + return; + } + for (uint16_t i = 0; i < size / 2; i++) { + if ((i % 256) == 0) { + upcase = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ); + if (!upcase) { + pr->println(F("read upcase failed")); + return; + } + } + if (i % 16 == 0) { + pr->println(); + printHex(pr, i); + } + pr->write(' '); + uint16_t uc = getLe16(&upcase[2 * (i & 0XFF)]); + printHex(pr, uc); + checksum = upcaseChecksum(uc, checksum); + } + pr->println(); + pr->print(F("checksum: ")); + printHex(pr, checksum); + pr->println(); +} +//------------------------------------------------------------------------------ +bool ExFatPartition::printVolInfo(print_t* pr) { + uint8_t* cache = dataCachePrepare(0, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("read mbr failed")); + return false; + } + const MbrSector_t* mbr = reinterpret_cast(cache); + printMbr(pr, mbr); + Sector_t startSector = getLe32(mbr->part->startSector); + Sector_t volSize = getLe32(mbr->part->totalSectors); + if (volSize == 0) { + pr->print(F("bad partition size")); + return false; + } + cache = dataCachePrepare(startSector, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("read pbs failed")); + return false; + } + printExFatBoot(pr, reinterpret_cast(cache)); + return true; +} +#endif // DOXYGEN_SHOULD_SKIP_THIS diff --git a/third_party/sdfat/src/ExFatLib/ExFatFile.cpp b/third_party/sdfat/src/ExFatLib/ExFatFile.cpp new file mode 100644 index 00000000..03b195c9 --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatFile.cpp @@ -0,0 +1,750 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFile.cpp" +#include "../common/DebugMacros.h" +#include "../common/FsUtf.h" +#include "ExFatLib.h" +//------------------------------------------------------------------------------ +/** test for legal character. + * + * \param[in] c character to be tested. + * + * \return true for legal character else false. + */ +inline bool lfnLegalChar(uint8_t c) { +#if USE_UTF8_LONG_NAMES + return !lfnReservedChar(c); +#else // USE_UTF8_LONG_NAMES + return !(lfnReservedChar(c) || c & 0X80); +#endif // USE_UTF8_LONG_NAMES +} +//------------------------------------------------------------------------------ +bool ExFatFile::attrib(uint8_t bits) { + if (!isFileOrSubDir() || (bits & FS_ATTRIB_USER_SETTABLE) != bits) { + DBG_FAIL_MACRO; + goto fail; + } + // Don't allow read-only to be set if the file is open for write. + if ((bits & FS_ATTRIB_READ_ONLY) && isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + m_attributes = (m_attributes & ~FS_ATTRIB_USER_SETTABLE) | bits; + // insure sync() will update dir entry + m_flags |= FILE_FLAG_DIR_DIRTY; + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint8_t* ExFatFile::dirCache(uint8_t set, uint8_t options) { + DirPos_t pos = m_dirPos; + if (m_vol->dirSeek(&pos, FS_DIR_SIZE * set) != 1) { + return nullptr; + } + return m_vol->dirCache(&pos, options); +} +//------------------------------------------------------------------------------ +bool ExFatFile::close() { + bool rtn = sync(); + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return rtn; +} +//------------------------------------------------------------------------------ +bool ExFatFile::contiguousRange(Sector_t* bgnSector, Sector_t* endSector) { + if (!isContiguous()) { + return false; + } + if (bgnSector) { + *bgnSector = firstSector(); + } + if (endSector) { + *endSector = + firstSector() + ((m_dataLength - 1) >> m_vol->bytesPerSectorShift()); + } + return true; +} +//------------------------------------------------------------------------------ +void ExFatFile::fgetpos(fspos_t* pos) const { + pos->position = m_curPosition; + pos->cluster = m_curCluster; +} +//------------------------------------------------------------------------------ +int ExFatFile::fgets(char* str, int num, const char* delim) { + char ch; + int n = 0; + int r = -1; + while ((n + 1) < num && (r = read(&ch, 1)) == 1) { + // delete CR + if (ch == '\r') { + continue; + } + str[n++] = ch; + if (!delim) { + if (ch == '\n') { + break; + } + } else { + if (strchr(delim, ch)) { + break; + } + } + } + if (r < 0) { + // read error + return -1; + } + str[n] = '\0'; + return n; +} +//------------------------------------------------------------------------------ +Sector_t ExFatFile::firstSector() const { + return m_firstCluster ? m_vol->clusterStartSector(m_firstCluster) : 0; +} +//------------------------------------------------------------------------------ +void ExFatFile::fsetpos(const fspos_t* pos) { + m_curPosition = pos->position; + m_curCluster = pos->cluster; +} +//------------------------------------------------------------------------------ +bool ExFatFile::getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { + const DirFile_t* df = reinterpret_cast( + m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(df->accessDate); + *ptime = getLe16(df->accessTime); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { + const DirFile_t* df = reinterpret_cast( + m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(df->createDate); + *ptime = getLe16(df->createTime); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) { + const DirFile_t* df = reinterpret_cast( + m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(df->modifyDate); + *ptime = getLe16(df->modifyTime); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::isBusy() { return m_vol->isBusy(); } +//------------------------------------------------------------------------------ +bool ExFatFile::open(const char* path, oflag_t oflag) { + return open(ExFatVolume::cwv(), path, oflag); +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(ExFatVolume* vol, const char* path, oflag_t oflag) { + return vol && open(vol->vwd(), path, oflag); +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(ExFatFile* dirFile, const char* path, oflag_t oflag) { + ExFatFile tmpDir; + ExName_t fname; + // error if already open + if (isOpen() || !dirFile->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (*path == 0) { + return openRoot(dirFile->m_vol); + } + if (!tmpDir.openRoot(dirFile->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + dirFile = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (*path == 0) { + break; + } + if (!openPrivate(dirFile, &fname, O_RDONLY)) { + DBG_WARN_MACRO; + goto fail; + } + tmpDir.copy(this); + dirFile = &tmpDir; + close(); + } + return openPrivate(dirFile, &fname, oflag); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(uint32_t index, oflag_t oflag) { + ExFatVolume* vol = ExFatVolume::cwv(); + return vol ? open(vol->vwd(), index, oflag) : false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(ExFatFile* dirFile, uint32_t index, oflag_t oflag) { + if (dirFile->seekSet(FS_DIR_SIZE * index) && openNext(dirFile, oflag)) { + if (dirIndex() == index) { + return true; + } + close(); + DBG_FAIL_MACRO; + } + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::openCwd() { + if (isOpen() || !ExFatVolume::cwv()) { + DBG_FAIL_MACRO; + goto fail; + } + this->copy(ExFatVolume::cwv()->vwd()); + rewind(); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::openNext(ExFatFile* dir, oflag_t oflag) { + if (isOpen() || !dir->isDir() || (dir->curPosition() & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + return openPrivate(dir, nullptr, oflag); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::openPrivate(ExFatFile* dir, ExName_t* fname, oflag_t oflag) { + int n; + uint8_t modeFlags; + uint8_t* cache __attribute__((unused)); + DirPos_t freePos __attribute__((unused)); + DirFile_t* dirFile; + DirStream_t* dirStream; + DirName_t* dirName; + uint8_t buf[FS_DIR_SIZE]; + uint8_t freeCount = 0; + uint8_t freeNeed = 3; + bool inSet = false; + + // error if already open, no access mode, or no directory. + if (isOpen() || !dir->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + + switch (oflag & O_ACCMODE) { + case O_RDONLY: + modeFlags = FILE_FLAG_READ; + break; + case O_WRONLY: + modeFlags = FILE_FLAG_WRITE; + break; + case O_RDWR: + modeFlags = FILE_FLAG_READ | FILE_FLAG_WRITE; + break; + default: + DBG_FAIL_MACRO; + goto fail; + } + modeFlags |= (oflag & O_APPEND) ? FILE_FLAG_APPEND : 0; + + if (fname) { + freeNeed = 2 + (fname->nameLength + 14) / 15; + dir->rewind(); + } + + while (1) { + n = dir->read(buf, FS_DIR_SIZE); + if (n == 0) { + goto create; + } + if (n != FS_DIR_SIZE) { + DBG_FAIL_MACRO; + goto fail; + } + if (!(buf[0] & EXFAT_TYPE_USED)) { + // Unused entry. + if (freeCount == 0) { + freePos.position = dir->curPosition() - FS_DIR_SIZE; + freePos.cluster = dir->curCluster(); + } + if (freeCount < freeNeed) { + freeCount++; + } + if (buf[0] == EXFAT_TYPE_END_DIR) { + if (fname) { + goto create; + } + // Likely openNext call. + DBG_WARN_MACRO; + goto fail; + } + inSet = false; + } else if (!inSet) { + if (freeCount < freeNeed) { + freeCount = 0; + } + if (buf[0] != EXFAT_TYPE_FILE) { + continue; + } + inSet = true; + memset(this, 0, sizeof(ExFatFile)); + dirFile = reinterpret_cast(buf); + m_setCount = dirFile->setCount; + m_attributes = getLe16(dirFile->attributes) & FS_ATTRIB_COPY; + if (!(m_attributes & FS_ATTRIB_DIRECTORY)) { + m_attributes |= FILE_ATTR_FILE; + } + m_vol = dir->volume(); + m_dirPos.cluster = dir->curCluster(); + m_dirPos.position = dir->curPosition() - FS_DIR_SIZE; + m_dirPos.isContiguous = dir->isContiguous(); + } else if (buf[0] == EXFAT_TYPE_STREAM) { + dirStream = reinterpret_cast(buf); + m_flags = modeFlags; + if (dirStream->flags & EXFAT_FLAG_CONTIGUOUS) { + m_flags |= FILE_FLAG_CONTIGUOUS; + } + m_validLength = getLe64(dirStream->validLength); + m_firstCluster = getLe32(dirStream->firstCluster); + m_dataLength = getLe64(dirStream->dataLength); + if (!fname) { + goto found; + } + fname->reset(); + if (fname->nameLength != dirStream->nameLength || + fname->nameHash != getLe16(dirStream->nameHash)) { + inSet = false; + } + } else if (buf[0] == EXFAT_TYPE_NAME) { + dirName = reinterpret_cast(buf); + if (!cmpName(dirName, fname)) { + inSet = false; + continue; + } + if (fname->atEnd()) { + goto found; + } + } else { + inSet = false; + } + } + +found: + // Don't open if create only. + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } + // Write, truncate, or at end is an error for a directory or read-only file. + if ((oflag & (O_TRUNC | O_AT_END)) || (m_flags & FILE_FLAG_WRITE)) { + if (isSubDir() || isReadOnly() || EXFAT_READ_ONLY) { + DBG_FAIL_MACRO; + goto fail; + } + } + +#if !EXFAT_READ_ONLY + if (oflag & O_TRUNC) { + if (!(m_flags & FILE_FLAG_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!truncate(0)) { + DBG_FAIL_MACRO; + goto fail; + } + } else if ((oflag & O_AT_END) && !seekSet(fileSize())) { + DBG_FAIL_MACRO; + goto fail; + } + if (isWritable()) { + m_attributes |= FS_ATTRIB_ARCHIVE; + } +#endif // !EXFAT_READ_ONLY + return true; + +create: +#if EXFAT_READ_ONLY + DBG_FAIL_MACRO; + goto fail; +#else // EXFAT_READ_ONLY + // don't create unless O_CREAT and write + if (!(oflag & O_CREAT) || !(modeFlags & FILE_FLAG_WRITE) || !fname) { + DBG_WARN_MACRO; + goto fail; + } + while (freeCount < freeNeed) { + n = dir->read(buf, FS_DIR_SIZE); + if (n == 0) { + Cluster_t saveCurCluster = dir->m_curCluster; + if (!dir->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + dir->m_curCluster = saveCurCluster; + continue; + } + if (n != FS_DIR_SIZE) { + DBG_FAIL_MACRO; + goto fail; + } + if (freeCount == 0) { + freePos.position = dir->curPosition() - FS_DIR_SIZE; + freePos.cluster = dir->curCluster(); + } + freeCount++; + } + freePos.isContiguous = dir->isContiguous(); + memset(this, 0, sizeof(ExFatFile)); + m_vol = dir->volume(); + m_attributes = FILE_ATTR_FILE | FS_ATTRIB_ARCHIVE; + m_dirPos = freePos; + fname->reset(); + for (uint8_t i = 0; i < freeNeed; i++) { + cache = dirCache(i, FsCache::CACHE_FOR_WRITE); + if (!cache || (cache[0] & 0x80)) { + DBG_FAIL_MACRO; + goto fail; + } + memset(cache, 0, FS_DIR_SIZE); + if (i == 0) { + dirFile = reinterpret_cast(cache); + dirFile->type = EXFAT_TYPE_FILE; + m_setCount = freeNeed - 1; + dirFile->setCount = m_setCount; + + if (FsDateTime::callback) { + uint16_t date, time; + uint8_t ms10; + FsDateTime::callback(&date, &time, &ms10); + setLe16(dirFile->createDate, date); + setLe16(dirFile->createTime, time); + dirFile->createTimeMs = ms10; + } else { + setLe16(dirFile->createDate, FS_DEFAULT_DATE); + setLe16(dirFile->modifyDate, FS_DEFAULT_DATE); + setLe16(dirFile->accessDate, FS_DEFAULT_DATE); + if (FS_DEFAULT_TIME) { + setLe16(dirFile->createTime, FS_DEFAULT_TIME); + setLe16(dirFile->modifyTime, FS_DEFAULT_TIME); + setLe16(dirFile->accessTime, FS_DEFAULT_TIME); + } + } + } else if (i == 1) { + dirStream = reinterpret_cast(cache); + dirStream->type = EXFAT_TYPE_STREAM; + dirStream->flags = EXFAT_FLAG_ALWAYS1; + m_flags = modeFlags | FILE_FLAG_DIR_DIRTY; + dirStream->nameLength = fname->nameLength; + setLe16(dirStream->nameHash, fname->nameHash); + } else { + dirName = reinterpret_cast(cache); + dirName->type = EXFAT_TYPE_NAME; + for (size_t k = 0; k < 15; k++) { + if (fname->atEnd()) { + break; + } + uint16_t u = fname->get16(); + setLe16(dirName->unicode + 2 * k, u); + } + } + } + return sync(); +#endif // EXFAT_READ_ONLY + +fail: + // close file + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::openRoot(ExFatVolume* vol) { + if (isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + memset(this, 0, sizeof(ExFatFile)); + m_attributes = FILE_ATTR_ROOT; + m_vol = vol; + m_flags = FILE_FLAG_READ; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::parsePathName(const char* path, ExName_t* fname, + const char** ptr) { + // Skip leading spaces. + while (*path == ' ') { + path++; + } + fname->begin = path; + fname->end = path; + while (*path && !isDirSeparator(*path)) { + uint8_t c = *path++; + if (!lfnLegalChar(c)) { + DBG_FAIL_MACRO; + goto fail; + } + if (c != '.' && c != ' ') { + // Need to trim trailing dots spaces. + fname->end = path; + } + } + // Advance to next path component. + for (; *path == ' ' || isDirSeparator(*path); path++) { + } + *ptr = path; + return hashName(fname); + +fail: + return false; +} +//------------------------------------------------------------------------------ +int ExFatFile::peek() { + uint64_t saveCurPosition = m_curPosition; + Cluster_t saveCurCluster = m_curCluster; + int c = read(); + m_curPosition = saveCurPosition; + m_curCluster = saveCurCluster; + return c; +} +//------------------------------------------------------------------------------ +int ExFatFile::read(void* buf, size_t count) { + uint8_t* dst = reinterpret_cast(buf); + int8_t fg; + uint64_t maxRead; + size_t toRead; + size_t toFill; + size_t rtn = 0; + size_t n; + uint8_t* cache; + uint16_t sectorOffset; + Sector_t sector; + uint32_t clusterOffset; + + if (!isReadable()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isContiguous() || isFile()) { + if (count > (m_dataLength - m_curPosition)) { + count = m_dataLength - m_curPosition; + } + maxRead = m_curPosition < m_validLength ? m_validLength - m_curPosition : 0; + toRead = count < maxRead ? count : maxRead; + toFill = count > toRead ? count - toRead : 0; + } else { + toRead = count; + toFill = 0; + } + while (toRead) { + clusterOffset = m_curPosition & m_vol->clusterMask(); + sectorOffset = clusterOffset & m_vol->sectorMask(); + if (clusterOffset == 0) { + if (m_curPosition == 0) { + m_curCluster = + isRoot() ? m_vol->rootDirectoryCluster() : m_firstCluster; + } else if (isContiguous()) { + m_curCluster++; + } else { + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg == 0) { + // EOF if directory. + if (isDir()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + } + } + sector = m_vol->clusterStartSector(m_curCluster) + + (clusterOffset >> m_vol->bytesPerSectorShift()); + if (sectorOffset != 0 || toRead < m_vol->bytesPerSector() || + sector == m_vol->dataCacheSector()) { + n = m_vol->bytesPerSector() - sectorOffset; + if (n > toRead) { + n = toRead; + } + // read sector to cache and copy data to caller + cache = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + const uint8_t* src = cache + sectorOffset; + memcpy(dst, src, n); +#if USE_MULTI_SECTOR_IO + } else if (toRead >= 2 * m_vol->bytesPerSector()) { + uint32_t ns = toRead >> m_vol->bytesPerSectorShift(); + // Limit reads to current cluster. + uint32_t maxNs = m_vol->sectorsPerCluster() - + (clusterOffset >> m_vol->bytesPerSectorShift()); + if (ns > maxNs) { + ns = maxNs; + } + n = ns << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeRead(sector, dst, ns)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + // read single sector + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeRead(sector, dst)) { + DBG_FAIL_MACRO; + goto fail; + } + } + dst += n; + rtn += n; + m_curPosition += n; + toRead -= n; + } + if (toFill) { + memset(dst, 0, toFill); + seekCur(toFill); + rtn += toFill; + } + return rtn; + +fail: + m_error |= READ_ERROR; + return -1; +} +//------------------------------------------------------------------------------ +bool ExFatFile::remove(const char* path) { + ExFatFile file; + if (!file.open(this, path, O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + return file.remove(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::seekSet(uint64_t pos) { + uint32_t nCur; + uint32_t nNew; + Cluster_t tmp = m_curCluster; + // error if file not open + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Optimize O_APPEND writes. + if (pos == m_curPosition) { + return true; + } + if (pos == 0) { + // set position to start of file + m_curCluster = 0; + goto done; + } + if (isFile()) { + if (pos > m_dataLength) { + DBG_FAIL_MACRO; + goto fail; + } + } + // calculate cluster index for new position + nNew = (pos - 1) >> m_vol->bytesPerClusterShift(); + if (isContiguous()) { + m_curCluster = m_firstCluster + nNew; + goto done; + } + // calculate cluster index for current position + nCur = (m_curPosition - 1) >> m_vol->bytesPerClusterShift(); + if (nNew < nCur || m_curPosition == 0) { + // must follow chain from first cluster + m_curCluster = isRoot() ? m_vol->rootDirectoryCluster() : m_firstCluster; + } else { + // advance from curPosition + nNew -= nCur; + } + while (nNew--) { + if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + +done: + m_curPosition = pos; + return true; + +fail: + m_curCluster = tmp; + return false; +} diff --git a/third_party/sdfat/src/ExFatLib/ExFatFile.h b/third_party/sdfat/src/ExFatLib/ExFatFile.h new file mode 100644 index 00000000..40f49331 --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatFile.h @@ -0,0 +1,906 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief ExFatFile class + */ +#include +#include + +#include "../common/FmtNumber.h" +#include "../common/FsApiConstants.h" +#include "../common/FsDateTime.h" +#include "../common/FsName.h" +#include "ExFatPartition.h" + +class ExFatVolume; +//------------------------------------------------------------------------------ +/** Expression for path name separator. */ +#define isDirSeparator(c) ((c) == '/') +//------------------------------------------------------------------------------ +/** + * \class ExName_t + * \brief Internal type for file name - do not use in user apps. + */ +class ExName_t : public FsName { + public: + /** Length of UTF-16 name */ + size_t nameLength; + /** Hash for UTF-16 name */ + uint16_t nameHash; +}; +//------------------------------------------------------------------------------ +/** + * \class ExFatFile + * \brief Basic file class. + */ +class ExFatFile { + public: + /** Create an instance. */ + ExFatFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). + */ + ExFatFile(const char* path, oflag_t oflag) { open(path, oflag); } + + /** Copy from to this. + * \param[in] from Source file. + */ + void copy(const ExFatFile* from) { + if (from != this) { +#if FILE_COPY_CONSTRUCTOR_SELECT + *this = *from; +#else // FILE_COPY_CONSTRUCTOR_SELECT + memcpy(this, from, sizeof(ExFatFile)); +#endif // FILE_COPY_CONSTRUCTOR_SELECT + } + } + /** move from to this. + * \param[in] from Source file. + */ + void move(ExFatFile* from) { + if (from != this) { + copy(from); + from->m_attributes = FILE_ATTR_CLOSED; + } + } + +#if FILE_COPY_CONSTRUCTOR_SELECT == FILE_COPY_CONSTRUCTOR_PUBLIC + /** Copy constructor. + * \param[in] from Move from file. + * + */ + ExFatFile(const ExFatFile& from) = default; + /** Copy assignment operator. + * \param[in] from Move from file. + * \return Copied file. + */ + ExFatFile& operator=(const ExFatFile& from) = default; +#elif FILE_COPY_CONSTRUCTOR_SELECT == FILE_COPY_CONSTRUCTOR_PRIVATE + + private: + ExFatFile(const ExFatFile& from) = default; + ExFatFile& operator=(const ExFatFile& from) = default; + + public: +#else // FILE_COPY_CONSTRUCTOR_SELECT + ExFatFile(const ExFatFile& from) = delete; + ExFatFile& operator=(const ExFatFile& from) = delete; +#endif // FILE_COPY_CONSTRUCTOR_SELECT + +#if FILE_MOVE_CONSTRUCTOR_SELECT + /** Move constructor. + * \param[in] from Move from file. + */ + ExFatFile(ExFatFile&& from) { move(&from); } + /** Move assignment operator. + * \param[in] from Move from file. + * \return Moved file. + */ + ExFatFile& operator=(ExFatFile&& from) { + move(&from); + return *this; + } +#else // FILE_MOVE_CONSTRUCTOR_SELECT + ExFatFile(ExFatFile&& from) = delete; + ExFatFile& operator=(ExFatFile&& from) = delete; +#endif + + /** Destructor */ +#if DESTRUCTOR_CLOSES_FILE + ~ExFatFile() { + if (isOpen()) { + close(); + } + } +#else // DESTRUCTOR_CLOSES_FILE + ~ExFatFile() = default; +#endif // DESTRUCTOR_CLOSES_FILE + + /** The parenthesis operator. + * + * \return true if a file is open. + */ + operator bool() { return isOpen(); } + /** + * \return user settable file attributes for success else -1. + */ + int attrib() { return isFileOrSubDir() ? m_attributes & FS_ATTRIB_COPY : -1; } + /** Set file attributes + * + * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY, + * FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE. + * + * \note attrib() will fail for set read-only if the file is open for write. + * \return true for success or false for failure. + */ + bool attrib(uint8_t bits); + /** \return The number of bytes available from the current position + * to EOF for normal files. INT_MAX is returned for very large files. + * + * available64() is recommended for very large files. + * + * Zero is returned for directory files. + * + */ + int available() { + uint64_t n = available64(); + return n > INT_MAX ? INT_MAX : n; + } + /** \return The number of bytes available from the current position + * to EOF for normal files. Zero is returned for directory files. + */ + uint64_t available64() { return isFile() ? fileSize() - curPosition() : 0; } + /** Clear all error bits. */ + void clearError() { m_error = 0; } + /** Clear writeError. */ + void clearWriteError() { m_error &= ~WRITE_ERROR; } + /** Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool close(); + /** Check for contiguous file and return its raw sector range. + * + * \param[out] bgnSector the first sector address for the file. + * \param[out] endSector the last sector address for the file. + * + * Parameters may be nullptr. + * + * \return true for success or false for failure. + */ + bool contiguousRange(Sector_t* bgnSector, Sector_t* endSector); + /** \return The current cluster number for a file or directory. */ + Cluster_t curCluster() const { return m_curCluster; } + /** \return The current position for a file or directory. */ + uint64_t curPosition() const { return m_curPosition; } + /** \return Total data length for file. */ + uint64_t dataLength() const { return m_dataLength; } + /** \return Directory entry index. */ + uint32_t dirIndex() const { return m_dirPos.position / FS_DIR_SIZE; } + /** \return The first cluster number for a file or directory. */ + Cluster_t firstCluster() const { return m_firstCluster; } + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * The calling instance must be an open directory file. + * + * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory + * dirFile. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + ExFatFile file; + return file.open(this, path, O_RDONLY); + } + /** get position for streams + * \param[out] pos struct to receive position + */ + void fgetpos(fspos_t* pos) const; + /** + * Get a string from a file. + * + * fgets() reads bytes from a file into the array pointed to by \a str, until + * \a num - 1 bytes are read, or a delimiter is read and transferred to + * \a str, or end-of-file is encountered. The string is then terminated + * with a null byte. + * + * fgets() deletes CR, '\\r', from the string. This insures only a '\\n' + * terminates the string for Windows text files which use CRLF for newline. + * + * \param[out] str Pointer to the array where the string is stored. + * \param[in] num Maximum number of characters to be read + * (including the final null byte). Usually the length + * of the array \a str is used. + * \param[in] delim Optional set of delimiters. The default is "\n". + * + * \return For success fgets() returns the length of the string in \a str. + * If no data is read, fgets() returns zero for EOF or -1 if an error + * occurred. + */ + int fgets(char* str, int num, const char* delim = nullptr); + /** \return The total number of bytes in a file. */ + uint64_t fileSize() const { return m_dataLength; } + /** \return Address of first sector or zero for empty file. */ + Sector_t firstSector() const; + /** Set position for streams + * \param[in] pos struct with value for new position + */ + void fsetpos(const fspos_t* pos); + /** Arduino name for sync() */ + void flush() { sync(); } + /** Get a file's access date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime); + /** Get a file's create date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime); + /** \return All error bits. */ + uint8_t getError() const { return isOpen() ? m_error : 0XFF; } + /** Get a file's modify date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime); + /** + * Get a file's name followed by a zero. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in characters. + * \return length for success or zero for failure. + */ + size_t getName(char* name, size_t size) { +#if USE_UTF8_LONG_NAMES + return getName8(name, size); +#else // USE_UTF8_LONG_NAMES + return getName7(name, size); +#endif // USE_UTF8_LONG_NAMES + } + /** + * Get a file's ASCII name followed by a zero. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in characters. + * \return the name length. + */ + size_t getName7(char* name, size_t size); + /** + * Get a file's UTF-8 name followed by a zero. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in characters. + * \return the name length. + */ + size_t getName8(char* name, size_t size); + /** \return value of writeError */ + bool getWriteError() const { return isOpen() ? m_error & WRITE_ERROR : true; } + /** + * Check for FsBlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy(); + /** \return True if the file is contiguous. */ + bool isContiguous() const { return m_flags & FILE_FLAG_CONTIGUOUS; } + /** \return True if this is a directory. */ + bool isDir() const { return m_attributes & FILE_ATTR_DIR; } + /** \return True if this is a normal file. */ + bool isFile() const { return m_attributes & FILE_ATTR_FILE; } + /** \return True if this is a normal file or sub-directory. */ + bool isFileOrSubDir() const { return isFile() || isSubDir(); } + /** \return True if this is a hidden. */ + bool isHidden() const { return m_attributes & FS_ATTRIB_HIDDEN; } + /** \return true if the file is open. */ + bool isOpen() const { return m_attributes; } + /** \return True if file is read-only */ + bool isReadOnly() const { return m_attributes & FS_ATTRIB_READ_ONLY; } + /** \return True if this is the root directory. */ + bool isRoot() const { return m_attributes & FILE_ATTR_ROOT; } + /** \return True file is readable. */ + bool isReadable() const { return m_flags & FILE_FLAG_READ; } + /** \return True if this is a sub-directory. */ + bool isSubDir() const { return m_attributes & FILE_ATTR_SUBDIR; } + /** \return True if this is a system file. */ + bool isSystem() const { return m_attributes & FS_ATTRIB_SYSTEM; } + /** \return True file is writable. */ + bool isWritable() const { return m_flags & FILE_FLAG_WRITE; } + /** List directory contents. + * + * \param[in] pr Print stream for list. + * \return true for success or false for failure. + */ + bool ls(print_t* pr); + /** List directory contents. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of sub-directories. + * + * \param[in] indent Amount of space before file name. Used for recursive + * list to indicate sub-directory level. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags, uint8_t indent = 0); + /** Make a new directory. + * + * \param[in] parent An open directory file that will + * contain the new directory. + * + * \param[in] path A path with a valid name for the new directory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(ExFatFile* parent, const char* path, bool pFlag = true); + /** Open a file or directory by name. + * + * \param[in] dirFile An open directory containing the file to be opened. + * + * \param[in] path The path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of flags from the following list. + * Only one of O_RDONLY, O_READ, O_WRONLY, O_WRITE, or + * O_RDWR is allowed. + * + * O_RDONLY - Open for reading. + * + * O_READ - Same as O_RDONLY. + * + * O_WRONLY - Open for writing. + * + * O_WRITE - Same as O_WRONLY. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_AT_END - Set the initial position at the end of the file. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file + * exists. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated + * to 0. + * + * WARNING: A given file must not be opened by more than one file object + * or file corruption may occur. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return true for success or false for failure. + */ + bool open(ExFatFile* dirFile, const char* path, oflag_t oflag = O_RDONLY); + /** Open a file in the volume working directory. + * + * \param[in] vol Volume where the file is located. + * + * \param[in] path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see open(ExFatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(ExFatVolume* vol, const char* path, oflag_t oflag = O_RDONLY); + /** Open a file by index. + * + * \param[in] dirFile An open ExFatFile instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see ExFatFile::open(ExFatFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(ExFatFile* dirFile, uint32_t index, oflag_t oflag = O_RDONLY); + /** Open a file by index in the current working directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(uint32_t index, oflag_t oflag = O_RDONLY); + /** Open a file in the current working directory. + * + * \param[in] path A path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see ExFatFile::open(ExFatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(const char* path, oflag_t oflag = O_RDONLY); + /** Open the current working directory. + * + * \return true for success or false for failure. + */ + bool openCwd(); + /** Open the next file or subdirectory in a directory. + * + * \param[in] dirFile An open instance for the directory + * containing the file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see open(ExFatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool openNext(ExFatFile* dirFile, oflag_t oflag = O_RDONLY); + /** Open a volume's root directory. + * + * \param[in] vol The FAT volume containing the root directory to be opened. + * + * \return true for success or false for failure. + */ + bool openRoot(ExFatVolume* vol); + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek(); + /** Allocate contiguous clusters to an empty file. + * + * The file must be empty with no clusters allocated. + * + * The file will have zero validLength and dataLength + * will equal the requested length. + * + * \param[in] length size of allocated space in bytes. + * \return true for success or false for failure. + */ + bool preAllocate(uint64_t length); + /** Print a file's access date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printAccessDateTime(print_t* pr); + /** Print a file's creation date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printCreateDateTime(print_t* pr); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, value, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + char sign = 0; + char buf[3 * sizeof(Type) + 3]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + if (value < 0) { + value = -value; + sign = '-'; + } + if (sizeof(Type) < 4) { + str = fmtBase10(str, static_cast(value)); + } else { + str = fmtBase10(str, static_cast(value)); + } + if (sign) { + *--str = sign; + } + return write(str, &buf[sizeof(buf)] - str); + } + /** Print a file's size in bytes. + * \param[in] pr Prtin stream for the output. + * \return The number of bytes printed. + */ + size_t printFileSize(print_t* pr); + /** Print a file's modify date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printModifyDateTime(print_t* pr); + /** Print a file's name + * + * \param[in] pr Print stream for output. + * + * \return length for success or zero for failure. + */ + size_t printName(print_t* pr) { +#if USE_UTF8_LONG_NAMES + return printName8(pr); +#else // USE_UTF8_LONG_NAMES + return printName7(pr); +#endif // USE_UTF8_LONG_NAMES + } + /** Print a file's ASCII name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName7(print_t* pr); + /** Print a file's UTF-8 name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName8(print_t* pr); + /** Read the next byte from a file. + * + * \return For success read returns the next byte in the file as an int. + * If an error occurs or end of file is reached -1 is returned. + */ + int read() { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + /** Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] count Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a nbyte, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. + */ + int read(void* buf, size_t count); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] path Path for the file to be removed. + * + * Example use: dirFile.remove(filenameToRemove); + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(const char* path); + /** Rename a file or subdirectory. + * + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(const char* newPath); + /** Rename a file or subdirectory. + * + * \param[in] dirFile Directory for the new path. + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(ExFatFile* dirFile, const char* newPath); + /** Set the file's current position to zero. */ + void rewind() { seekSet(0); } + /** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmdir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return true for success or false for failure. + */ + bool rmdir(); + /** Set the files position to current position + \a pos. See seekSet(). + * \param[in] offset The new position in bytes from the current position. + * \return true for success or false for failure. + */ + bool seekCur(int64_t offset) { return seekSet(m_curPosition + offset); } + /** Set the files position to end-of-file + \a offset. See seekSet(). + * Can't be used for directory files since file size is not defined. + * \param[in] offset The new position in bytes from end-of-file. + * \return true for success or false for failure. + */ + bool seekEnd(int64_t offset = 0) { + return isFile() ? seekSet(m_dataLength + offset) : false; + } + /** Sets a file's position. + * + * \param[in] pos The new position in bytes from the beginning of the file. + * + * \return true for success or false for failure. + */ + bool seekSet(uint64_t pos); + /** \return directory set count */ + uint8_t setCount() const { return m_setCount; } + /** The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool sync(); + /** Truncate a file at the current file position. + * + * \return true for success or false for failure. + */ + /** Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a + * bitwise-inclusive OR of flags from the following list + * + * T_ACCESS - Set the file's last access date and time. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2099 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return true for success or false for failure. + */ + bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second); + /** Truncate a file at the current file position. + * will be maintained if it is less than or equal to \a length otherwise + * it will be set to end of file. + * + * \return true for success or false for failure. + */ + bool truncate(); + /** Truncate a file to a specified length. The current file position + * will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(uint64_t length) { return seekSet(length) && truncate(); } + + /** \return The valid number of bytes in a file. */ + uint64_t validLength() const { return m_validLength; } + /** Write a string to a file. Used by the Arduino Print class. + * \param[in] str Pointer to the string. + * Use getWriteError to check for errors. + * \return count of characters written for success or -1 for failure. + */ + size_t write(const char* str) { return write(str, strlen(str)); } + /** Write a single byte. + * \param[in] b The byte to be written. + * \return +1 for success or zero for failure. + */ + size_t write(uint8_t b) { return write(&b, 1); } + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] count Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a count. If an error occurs, write() returns zero and writeError is set. + */ + size_t write(const void* buf, size_t count); +//------------------------------------------------------------------------------ +#if ENABLE_ARDUINO_SERIAL + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags = 0) { return ls(&Serial, flags); } + /** Print a file's name. + * + * \return length for success or zero for failure. + */ + size_t printName() { return ExFatFile::printName(&Serial); } +#endif // ENABLE_ARDUINO_SERIAL + + private: + /** ExFatVolume allowed access to private members. */ + friend class ExFatVolume; + bool addCluster(); + bool addDirCluster(); + bool cmpName(const DirName_t* dirName, ExName_t* fname); + uint8_t* dirCache(uint8_t set, uint8_t options); + bool hashName(ExName_t* fname); + bool mkdir(ExFatFile* parent, ExName_t* fname); + + bool openPrivate(ExFatFile* dir, ExName_t* fname, oflag_t oflag); + bool parsePathName(const char* path, ExName_t* fname, const char** ptr); + ExFatVolume* volume() const { return m_vol; } + bool syncDir(); + //---------------------------------------------------------------------------- + static const uint8_t WRITE_ERROR = 0X1; + static const uint8_t READ_ERROR = 0X2; + + /** This file has not been opened. */ + static const uint8_t FILE_ATTR_CLOSED = 0; + /** Entry for normal data file */ + static const uint8_t FILE_ATTR_FILE = 0X08; + /** Entry is for a subdirectory */ + static const uint8_t FILE_ATTR_SUBDIR = FS_ATTRIB_DIRECTORY; + /** Root directory */ + static const uint8_t FILE_ATTR_ROOT = 0X40; + /** Directory type bits */ + static const uint8_t FILE_ATTR_DIR = FILE_ATTR_SUBDIR | FILE_ATTR_ROOT; + + static const uint8_t FILE_FLAG_READ = 0X01; + static const uint8_t FILE_FLAG_WRITE = 0X02; + static const uint8_t FILE_FLAG_APPEND = 0X08; + static const uint8_t FILE_FLAG_CONTIGUOUS = 0X40; + static const uint8_t FILE_FLAG_DIR_DIRTY = 0X80; + + uint64_t m_curPosition; + uint64_t m_dataLength; + uint64_t m_validLength; + Cluster_t m_curCluster; + Cluster_t m_firstCluster; + ExFatVolume* m_vol; + DirPos_t m_dirPos; + uint8_t m_setCount; + uint8_t m_attributes = FILE_ATTR_CLOSED; + uint8_t m_error = 0; + uint8_t m_flags = 0; +}; +#include "../common/ArduinoFiles.h" +/** + * \class ExFile + * \brief exFAT file with Arduino Stream. + */ +class ExFile : public StreamFile { + public: + ExFile() {} + /** Create an open ExFile. + * \param[in] path path for file. + * \param[in] oflag open flags. + */ + ExFile(const char* path, oflag_t oflag) { open(path, oflag); } + /** Opens the next file or folder in a directory. + * + * \param[in] oflag open flags. + * \return a FatStream object. + */ + ExFile openNextFile(oflag_t oflag = O_RDONLY) { + ExFile tmpFile; + tmpFile.openNext(this, oflag); + return tmpFile; + } +}; diff --git a/third_party/sdfat/src/ExFatLib/ExFatFilePrint.cpp b/third_party/sdfat/src/ExFatLib/ExFatFilePrint.cpp new file mode 100644 index 00000000..6664b47a --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatFilePrint.cpp @@ -0,0 +1,226 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFilePrint.cpp" +#include "../common/DebugMacros.h" +#include "../common/FsUtf.h" +#include "ExFatLib.h" +//------------------------------------------------------------------------------ +bool ExFatFile::ls(print_t* pr) { + ExFatFile file; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (file.openNext(this, O_RDONLY)) { + if (!file.isHidden()) { + file.printName(pr); + if (file.isDir()) { + pr->write('/'); + } + pr->write('\r'); + pr->write('\n'); + } + file.close(); + } + if (getError()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) { + ExFatFile file; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (file.openNext(this, O_RDONLY)) { + // indent for dir level + if (!file.isHidden() || (flags & LS_A)) { + for (uint8_t i = 0; i < indent; i++) { + pr->write(' '); + } + if (flags & LS_DATE) { + file.printModifyDateTime(pr); + pr->write(' '); + } + if (flags & LS_SIZE) { + file.printFileSize(pr); + pr->write(' '); + } + file.printName(pr); + if (file.isDir()) { + pr->write('/'); + } + pr->write('\r'); + pr->write('\n'); + if ((flags & LS_R) && file.isDir()) { + file.ls(pr, flags, indent + 2); + } + } + file.close(); + } + if (getError()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printAccessDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getAccessDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printCreateDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getCreateDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printFileSize(print_t* pr) { + uint64_t n = fileSize(); + char buf[21]; + char* str = &buf[sizeof(buf) - 1]; + char* bgn = str - 12; + *str = '\0'; + do { + uint64_t m = n; + n /= 10; + *--str = m - 10 * n + '0'; + } while (n); + while (str > bgn) { + *--str = ' '; + } + return pr->write(str); +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printModifyDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getModifyDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printName7(print_t* pr) { + const DirName_t* dn; + size_t n = 0; + uint8_t in; + uint8_t buf[15]; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t is = 2; is <= m_setCount; is++) { + dn = reinterpret_cast(dirCache(is, FsCache::CACHE_FOR_READ)); + if (!dn || dn->type != EXFAT_TYPE_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + for (in = 0; in < 15; in++) { + uint16_t c = getLe16(dn->unicode + 2 * in); + if (!c) { + break; + } + buf[in] = c < 0X7F ? c : '?'; + n++; + } + pr->write(buf, in); + } + return n; + +fail: + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printName8(print_t* pr) { + const DirName_t* dn; + uint16_t hs = 0; + uint32_t cp; + size_t n = 0; + uint8_t in; + char buf[5]; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t is = 2; is <= m_setCount; is++) { + dn = reinterpret_cast(dirCache(is, FsCache::CACHE_FOR_READ)); + if (!dn || dn->type != EXFAT_TYPE_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + for (in = 0; in < 15; in++) { + uint16_t c = getLe16(dn->unicode + 2 * in); + if (hs) { + if (!FsUtf::isLowSurrogate(c)) { + DBG_FAIL_MACRO; + goto fail; + } + cp = FsUtf::u16ToCp(hs, c); + hs = 0; + } else if (!FsUtf::isSurrogate(c)) { + if (c == 0) { + break; + } + cp = c; + } else if (FsUtf::isHighSurrogate(c)) { + hs = c; + continue; + } else { + DBG_FAIL_MACRO; + goto fail; + } + const char* str = FsUtf::cpToMb(cp, buf, buf + sizeof(buf)); + if (!str) { + DBG_FAIL_MACRO; + goto fail; + } + n += pr->write(reinterpret_cast(buf), str - buf); + } + } + return n; + +fail: + return 0; +} diff --git a/third_party/sdfat/src/ExFatLib/ExFatFileWrite.cpp b/third_party/sdfat/src/ExFatLib/ExFatFileWrite.cpp new file mode 100644 index 00000000..fcb63f2f --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatFileWrite.cpp @@ -0,0 +1,765 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFileWrite.cpp" +#include "../common/DateLib.h" +#include "../common/DebugMacros.h" +#include "ExFatLib.h" +//============================================================================== +#if EXFAT_READ_ONLY +bool ExFatFile::mkdir(ExFatFile* parent, const char* path, bool pFlag) { + (void)parent; + (void)path; + (void)pFlag; + return false; +} +bool ExFatFile::preAllocate(uint64_t length) { + (void)length; + return false; +} +bool ExFatFile::rename(const char* newPath) { + (void)newPath; + return false; +} +bool ExFatFile::rename(ExFatFile* dirFile, const char* newPath) { + (void)dirFile; + (void)newPath; + return false; +} +bool ExFatFile::sync() { return false; } +bool ExFatFile::truncate() { return false; } +size_t ExFatFile::write(const void* buf, size_t nbyte) { + (void)buf; + (void)nbyte; + return false; +} +//============================================================================== +#else // EXFAT_READ_ONLY +//------------------------------------------------------------------------------ +static uint16_t exFatDirChecksum(const uint8_t* data, uint16_t checksum) { + bool skip = data[0] == EXFAT_TYPE_FILE; + for (size_t i = 0; i < 32; i += i == 1 && skip ? 3 : 1) { + checksum = ((checksum << 15) | (checksum >> 1)) + data[i]; + } + return checksum; +} +//------------------------------------------------------------------------------ +bool ExFatFile::addCluster() { + Cluster_t find = m_vol->bitmapFind(m_curCluster ? m_curCluster + 1 : 0, 1); + if (find < 2) { + DBG_FAIL_MACRO; + goto fail; + } + if (!m_vol->bitmapModify(find, 1, 1)) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_curCluster == 0) { + m_flags |= FILE_FLAG_CONTIGUOUS; + goto done; + } + if (isContiguous()) { + if (find == (m_curCluster + 1)) { + goto done; + } + // No longer contiguous so make FAT chain. + m_flags &= ~FILE_FLAG_CONTIGUOUS; + + for (Cluster_t c = m_firstCluster; c < m_curCluster; c++) { + if (!m_vol->fatPut(c, c + 1)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + // New cluster is EOC. + if (!m_vol->fatPut(find, EXFAT_EOC)) { + DBG_FAIL_MACRO; + goto fail; + } + // Connect new cluster to existing chain. + if (m_curCluster) { + if (!m_vol->fatPut(m_curCluster, find)) { + DBG_FAIL_MACRO; + goto fail; + } + } + +done: + m_curCluster = find; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::addDirCluster() { + Sector_t sector; + uint32_t dl = isRoot() ? m_vol->rootLength() : m_dataLength; + uint8_t* cache; + dl += m_vol->bytesPerCluster(); + if (dl >= 0X4000000) { + DBG_FAIL_MACRO; + goto fail; + } + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + sector = m_vol->clusterStartSector(m_curCluster); + for (uint32_t i = 0; i < m_vol->sectorsPerCluster(); i++) { + cache = + m_vol->dataCachePrepare(sector + i, FsCache::CACHE_RESERVE_FOR_WRITE); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + memset(cache, 0, m_vol->bytesPerSector()); + } + if (!isRoot()) { + m_flags |= FILE_FLAG_DIR_DIRTY; + m_dataLength += m_vol->bytesPerCluster(); + m_validLength += m_vol->bytesPerCluster(); + } + return sync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::mkdir(ExFatFile* parent, const char* path, bool pFlag) { + ExName_t fname; + ExFatFile tmpDir; + + if (isOpen() || !parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (!tmpDir.openRoot(parent->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + parent = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!*path) { + break; + } + if (!openPrivate(parent, &fname, O_RDONLY)) { + if (!pFlag || !mkdir(parent, &fname)) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmpDir.copy(this); + parent = &tmpDir; + close(); + } + return mkdir(parent, &fname); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::mkdir(ExFatFile* parent, ExName_t* fname) { + if (!parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + // create a normal file + if (!openPrivate(parent, fname, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + // convert file to directory + m_attributes = FILE_ATTR_SUBDIR | FS_ATTRIB_ARCHIVE; + + // allocate and zero first cluster + if (!addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + + // Set to start of dir + rewind(); + m_flags = FILE_FLAG_READ | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY; + return sync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::preAllocate(uint64_t length) { + uint32_t find; + uint32_t need; + if (!length || !isWritable() || m_firstCluster) { + DBG_FAIL_MACRO; + goto fail; + } + need = 1 + ((length - 1) >> m_vol->bytesPerClusterShift()); + find = m_vol->bitmapFind(0, need); + if (find < 2) { + DBG_FAIL_MACRO; + goto fail; + } + if (!m_vol->bitmapModify(find, need, 1)) { + DBG_FAIL_MACRO; + goto fail; + } + m_dataLength = length; + m_firstCluster = find; + m_flags |= FILE_FLAG_DIR_DIRTY | FILE_FLAG_CONTIGUOUS; + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::remove() { + uint8_t* cache; + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster) { + if (isContiguous()) { + uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift()); + if (!m_vol->bitmapModify(m_firstCluster, nc, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + if (!m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + + for (uint8_t is = 0; is <= m_setCount; is++) { + cache = dirCache(is, FsCache::CACHE_FOR_WRITE); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + // Mark entry not used. + cache[0] &= 0x7F; + } + // Set this file closed. + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + + // Write entry to device. + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::rename(const char* newPath) { + return rename(m_vol->vwd(), newPath); +} +//------------------------------------------------------------------------------ +bool ExFatFile::rename(ExFatFile* dirFile, const char* newPath) { + ExFatFile file; + ExFatFile oldFile; + + // Must be an open file or subdirectory. + if (!(isFile() || isSubDir())) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't move file to new volume. + if (m_vol != dirFile->m_vol) { + DBG_FAIL_MACRO; + goto fail; + } + if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + oldFile.copy(this); + m_dirPos = file.m_dirPos; + m_setCount = file.m_setCount; + m_flags |= FILE_FLAG_DIR_DIRTY; + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // Remove old directory entry; + oldFile.m_firstCluster = 0; + oldFile.m_flags = FILE_FLAG_WRITE; + oldFile.m_attributes = FILE_ATTR_FILE; + return oldFile.remove(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::rmdir() { + int n; + uint8_t dir[FS_DIR_SIZE]; + // must be open subdirectory + if (!isSubDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + + // make sure directory is empty + while (1) { + n = read(dir, FS_DIR_SIZE); + if (n == 0) { + break; + } + if (n != FS_DIR_SIZE || dir[0] & 0X80) { + DBG_FAIL_MACRO; + goto fail; + } + if (dir[0] == 0) { + break; + } + } + // convert empty directory to normal file for remove + m_attributes = FILE_ATTR_FILE; + m_flags |= FILE_FLAG_WRITE; + return remove(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::sync() { + if (!isOpen()) { + return true; + } + if (m_flags & FILE_FLAG_DIR_DIRTY) { + // clear directory dirty + m_flags &= ~FILE_FLAG_DIR_DIRTY; + return syncDir(); + } + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + m_error |= WRITE_ERROR; + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::syncDir() { + DirFile_t* df; + DirStream_t* ds; + uint8_t* cache; + uint16_t checksum = 0; + + for (uint8_t is = 0; is <= m_setCount; is++) { + cache = dirCache(is, FsCache::CACHE_FOR_READ); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + switch (cache[0]) { + case EXFAT_TYPE_FILE: + df = reinterpret_cast(cache); + setLe16(df->attributes, m_attributes & FS_ATTRIB_COPY); + if (FsDateTime::callback) { + uint16_t date, time; + uint8_t ms10; + FsDateTime::callback(&date, &time, &ms10); + df->modifyTimeMs = ms10; + setLe16(df->modifyTime, time); + setLe16(df->modifyDate, date); + setLe16(df->accessTime, time); + setLe16(df->accessDate, date); + } + m_vol->dataCacheDirty(); + break; + + case EXFAT_TYPE_STREAM: + ds = reinterpret_cast(cache); + if (isContiguous()) { + ds->flags |= EXFAT_FLAG_CONTIGUOUS; + } else { + ds->flags &= ~EXFAT_FLAG_CONTIGUOUS; + } + setLe64(ds->validLength, m_validLength); + setLe32(ds->firstCluster, m_firstCluster); + setLe64(ds->dataLength, m_dataLength); + m_vol->dataCacheDirty(); + break; + + case EXFAT_TYPE_NAME: + break; + + default: + DBG_FAIL_MACRO; + goto fail; + break; + } + checksum = exFatDirChecksum(cache, checksum); + } + df = reinterpret_cast( + m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_WRITE)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + setLe16(df->setChecksum, checksum); + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + m_error |= WRITE_ERROR; + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, + uint8_t day, uint8_t hour, uint8_t minute, + uint8_t second) { + DirFile_t* df; + uint8_t* cache; + uint16_t checksum = 0; + uint16_t date; + uint16_t time; + uint8_t ms10; + + if (!isFileOrSubDir() || year < 1980 || year > 2099 || month < 1 || + month > 12 || day < 1 || day > daysInMonth(year, month) || hour > 23 || + minute > 59 || second > 59) { + DBG_FAIL_MACRO; + goto fail; + } + // update directory entry + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + + date = FS_DATE(year, month, day); + time = FS_TIME(hour, minute, second); + ms10 = second & 1 ? 100 : 0; + + for (uint8_t is = 0; is <= m_setCount; is++) { + cache = dirCache(is, FsCache::CACHE_FOR_READ); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + switch (cache[0]) { + case EXFAT_TYPE_FILE: + df = reinterpret_cast(cache); + setLe16(df->attributes, m_attributes & FS_ATTRIB_COPY); + m_vol->dataCacheDirty(); + if (flags & T_ACCESS) { + setLe16(df->accessTime, time); + setLe16(df->accessDate, date); + } + if (flags & T_CREATE) { + df->createTimeMs = ms10; + setLe16(df->createTime, time); + setLe16(df->createDate, date); + } + if (flags & T_WRITE) { + df->modifyTimeMs = ms10; + setLe16(df->modifyTime, time); + setLe16(df->modifyDate, date); + } + break; + + case EXFAT_TYPE_STREAM: + break; + + case EXFAT_TYPE_NAME: + break; + + default: + DBG_FAIL_MACRO; + goto fail; + break; + } + checksum = exFatDirChecksum(cache, checksum); + } + df = reinterpret_cast( + m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_WRITE)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + setLe16(df->setChecksum, checksum); + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::truncate() { + uint32_t toFree; + // error if not a normal file or read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_firstCluster == 0) { + return true; + } + if (isContiguous()) { + uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift()); + if (m_curCluster) { + toFree = m_curCluster + 1; + nc -= 1 + m_curCluster - m_firstCluster; + } else { + toFree = m_firstCluster; + m_firstCluster = 0; + } + if (nc && !m_vol->bitmapModify(toFree, nc, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // need to free chain + if (m_curCluster) { + toFree = 0; + int8_t fg = m_vol->fatGet(m_curCluster, &toFree); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg) { + // current cluster is end of chain + if (!m_vol->fatPut(m_curCluster, EXFAT_EOC)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + toFree = m_firstCluster; + m_firstCluster = 0; + } + if (toFree) { + if (!m_vol->freeChain(toFree)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + m_validLength = m_curPosition > m_validLength ? m_validLength : m_curPosition; + m_dataLength = m_curPosition; + m_flags |= FILE_FLAG_DIR_DIRTY; + return sync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::write(const void* buf, size_t nbyte) { + // convert void* to uint8_t* - must be before goto statements + const uint8_t* src = reinterpret_cast(buf); + uint8_t* cache; + uint8_t cacheOption; + uint16_t sectorOffset; + Sector_t sector; + uint32_t clusterOffset; + uint64_t toFill = 0; + size_t toWrite = nbyte; + size_t n; + + // error if not an open file or is read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // seek to end of file if append flag + if ((m_flags & FILE_FLAG_APPEND)) { + if (!seekSet(m_dataLength)) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (m_curPosition > m_validLength) { + toFill = m_curPosition - m_validLength; + if (!seekSet(m_validLength)) { + DBG_FAIL_MACRO; + goto fail; + } + } + while (toWrite) { + clusterOffset = m_curPosition & m_vol->clusterMask(); + sectorOffset = clusterOffset & m_vol->sectorMask(); + if (clusterOffset == 0) { + // start of new cluster + if (m_curCluster != 0) { + int fg; + + if (isContiguous()) { + Cluster_t lc = m_firstCluster; + lc += (m_dataLength - 1) >> m_vol->bytesPerClusterShift(); + if (m_curCluster < lc) { + m_curCluster++; + fg = 1; + } else { + fg = 0; + } + } else { + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (fg == 0) { + // add cluster if at end of chain + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + if (m_firstCluster == 0) { + // allocate first cluster of file + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + } else { + m_curCluster = m_firstCluster; + } + } + } + // sector for data write + sector = m_vol->clusterStartSector(m_curCluster) + + (clusterOffset >> m_vol->bytesPerSectorShift()); + + if (sectorOffset != 0 || toWrite < m_vol->bytesPerSector() || toFill) { + // partial sector - must use cache + // max space in sector + n = m_vol->bytesPerSector() - sectorOffset; + + if (sectorOffset == 0 && m_curPosition >= m_validLength) { + // start of new sector don't need to read into cache + cacheOption = FsCache::CACHE_RESERVE_FOR_WRITE; + } else { + // rewrite part of sector + cacheOption = FsCache::CACHE_FOR_WRITE; + } + cache = m_vol->dataCachePrepare(sector, cacheOption); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* dst = cache + sectorOffset; + + if (toFill) { + if (n > toFill) { + n = toFill; + } + memset(dst, 0, n); + } else { + if (n > toWrite) { + n = toWrite; + } + memcpy(dst, src, n); + } + if (m_vol->bytesPerSector() == (n + sectorOffset)) { + // Force write if sector is full - improves large writes. + if (!m_vol->dataCacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + } +#if USE_MULTI_SECTOR_IO + } else if (toWrite >= 2 * m_vol->bytesPerSector()) { + // use multiple sector write command + uint32_t ns = toWrite >> m_vol->bytesPerSectorShift(); + // Limit writes to current cluster. + uint32_t maxNs = m_vol->sectorsPerCluster() - + (clusterOffset >> m_vol->bytesPerSectorShift()); + if (ns > maxNs) { + ns = maxNs; + } + n = ns << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeWrite(sector, src, ns)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeWrite(sector, src)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_curPosition += n; + if (toFill) { + toFill -= n; + } else { + src += n; + toWrite -= n; + } + if (m_curPosition > m_validLength) { + m_flags |= FILE_FLAG_DIR_DIRTY; + m_validLength = m_curPosition; + } + } + if (m_curPosition > m_dataLength) { + m_dataLength = m_curPosition; + // update fileSize and insure sync will update dir entry + m_flags |= FILE_FLAG_DIR_DIRTY; + } else if (FsDateTime::callback) { + // insure sync will update modified date and time + m_flags |= FILE_FLAG_DIR_DIRTY; + } + return nbyte; + +fail: + // return for write error + m_error |= WRITE_ERROR; + return 0; +} +#endif // EXFAT_READ_ONLY diff --git a/third_party/sdfat/src/ExFatLib/ExFatFormatter.cpp b/third_party/sdfat/src/ExFatLib/ExFatFormatter.cpp new file mode 100644 index 00000000..9417ba22 --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatFormatter.cpp @@ -0,0 +1,367 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFormatter.cpp" +#include "../common/DebugMacros.h" +#include "../common/upcase.h" +#include "ExFatLib.h" +//------------------------------------------------------------------------------ +// Formatter assumes 512 byte sectors. +const uint32_t BOOT_BACKUP_OFFSET = 12; +const uint16_t BYTES_PER_SECTOR = 512; +const uint16_t SECTOR_MASK = BYTES_PER_SECTOR - 1; +const uint8_t BYTES_PER_SECTOR_SHIFT = 9; +const uint16_t MINIMUM_UPCASE_SKIP = 512; +const Cluster_t BITMAP_CLUSTER = 2; +const Cluster_t UPCASE_CLUSTER = 3; +const Cluster_t ROOT_CLUSTER = 4; +//------------------------------------------------------------------------------ +#define PRINT_FORMAT_PROGRESS 1 +#if !PRINT_FORMAT_PROGRESS +#define writeMsg(pr, str) +#elif defined(__AVR__) +#define writeMsg(pr, str) \ + if (pr) pr->print(F(str)) +#else // PRINT_FORMAT_PROGRESS +#define writeMsg(pr, str) \ + if (pr) pr->write(str) +#endif // PRINT_FORMAT_PROGRESS +//------------------------------------------------------------------------------ +bool ExFatFormatter::format(FsBlockDevice* dev, uint8_t* secBuf, print_t* pr) { +#if !PRINT_FORMAT_PROGRESS + (void)pr; +#endif // !PRINT_FORMAT_PROGRESS + MbrSector_t* mbr; + ExFatPbs_t* pbs; + DirUpcase_t* dup; + DirBitmap_t* dbm; + DirLabel_t* label; + uint32_t bitmapSize; + uint32_t checksum = 0; + Cluster_t clusterCount; + Cluster_t clusterHeapOffset; + uint32_t fatLength; + uint32_t fatOffset; + uint32_t m; + uint32_t ns; + uint32_t partitionOffset; + Sector_t sector; + Sector_t sectorsPerCluster; + uint32_t volumeLength; + Sector_t sectorCount; + uint8_t sectorsPerClusterShift; + uint8_t vs; + + m_dev = dev; + m_secBuf = secBuf; + sectorCount = dev->sectorCount(); + // Min size is 512 MB + if (sectorCount < 0X100000) { + writeMsg(pr, "Device is too small\r\n"); + DBG_FAIL_MACRO; + goto fail; + } + // Determine partition layout. + for (m = 1, vs = 0; m && sectorCount > m; m <<= 1, vs++) { + } + sectorsPerClusterShift = vs < 29 ? 8 : (vs - 11) / 2; + sectorsPerCluster = 1UL << sectorsPerClusterShift; + fatLength = 1UL << (vs < 27 ? 13 : (vs + 1) / 2); + fatOffset = fatLength; + partitionOffset = 2 * fatLength; + clusterHeapOffset = 2 * fatLength; + clusterCount = (sectorCount - 4 * fatLength) >> sectorsPerClusterShift; + volumeLength = clusterHeapOffset + (clusterCount << sectorsPerClusterShift); + + // make Master Boot Record. Use fake CHS. + memset(secBuf, 0, BYTES_PER_SECTOR); + mbr = reinterpret_cast(secBuf); + mbr->part->beginCHS[0] = 1; + mbr->part->beginCHS[1] = 1; + mbr->part->beginCHS[2] = 0; + mbr->part->type = 7; + mbr->part->endCHS[0] = 0XFE; + mbr->part->endCHS[1] = 0XFF; + mbr->part->endCHS[2] = 0XFF; + setLe32(mbr->part->startSector, partitionOffset); + setLe32(mbr->part->totalSectors, volumeLength); + setLe16(mbr->signature, MBR_SIGNATURE); + if (!dev->writeSector(0, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + // Partition Boot sector. + memset(secBuf, 0, BYTES_PER_SECTOR); + pbs = reinterpret_cast(secBuf); + pbs->jmpInstruction[0] = 0XEB; + pbs->jmpInstruction[1] = 0X76; + pbs->jmpInstruction[2] = 0X90; + pbs->oemName[0] = 'E'; + pbs->oemName[1] = 'X'; + pbs->oemName[2] = 'F'; + pbs->oemName[3] = 'A'; + pbs->oemName[4] = 'T'; + pbs->oemName[5] = ' '; + pbs->oemName[6] = ' '; + pbs->oemName[7] = ' '; + setLe64(pbs->bpb.partitionOffset, partitionOffset); + setLe64(pbs->bpb.volumeLength, volumeLength); + setLe32(pbs->bpb.fatOffset, fatOffset); + setLe32(pbs->bpb.fatLength, fatLength); + setLe32(pbs->bpb.clusterHeapOffset, clusterHeapOffset); + setLe32(pbs->bpb.clusterCount, clusterCount); + setLe32(pbs->bpb.rootDirectoryCluster, ROOT_CLUSTER); + setLe32(pbs->bpb.volumeSerialNumber, sectorCount); + setLe16(pbs->bpb.fileSystemRevision, 0X100); + setLe16(pbs->bpb.volumeFlags, 0); + pbs->bpb.bytesPerSectorShift = BYTES_PER_SECTOR_SHIFT; + pbs->bpb.sectorsPerClusterShift = sectorsPerClusterShift; + pbs->bpb.numberOfFats = 1; + pbs->bpb.driveSelect = 0X80; + pbs->bpb.percentInUse = 0; + + // Fill boot code like official SDFormatter. + for (size_t i = 0; i < sizeof(pbs->bootCode); i++) { + pbs->bootCode[i] = 0XF4; + } + setLe16(pbs->signature, PBR_SIGNATURE); + for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { + if (i == offsetof(ExFatPbs_t, bpb.volumeFlags[0]) || + i == offsetof(ExFatPbs_t, bpb.volumeFlags[1]) || + i == offsetof(ExFatPbs_t, bpb.percentInUse)) { + continue; + } + checksum = exFatChecksum(checksum, secBuf[i]); + } + sector = partitionOffset; + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + sector++; + // Write eight Extended Boot Sectors. + memset(secBuf, 0, BYTES_PER_SECTOR); + setLe16(pbs->signature, PBR_SIGNATURE); + for (int j = 0; j < 8; j++) { + for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { + checksum = exFatChecksum(checksum, secBuf[i]); + } + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + sector++; + } + // Write OEM Parameter Sector and reserved sector. + memset(secBuf, 0, BYTES_PER_SECTOR); + for (int j = 0; j < 2; j++) { + for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { + checksum = exFatChecksum(checksum, secBuf[i]); + } + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + sector++; + } + // Write Boot CheckSum Sector. + for (size_t i = 0; i < BYTES_PER_SECTOR; i += 4) { + setLe32(secBuf + i, checksum); + } + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + // Initialize FAT. + writeMsg(pr, "Writing FAT "); + sector = partitionOffset + fatOffset; + ns = ((clusterCount + 2) * 4 + BYTES_PER_SECTOR - 1) / BYTES_PER_SECTOR; + + memset(secBuf, 0, BYTES_PER_SECTOR); + // Allocate two reserved clusters, bitmap, upcase, and root clusters. + secBuf[0] = 0XF8; + for (size_t i = 1; i < 20; i++) { + secBuf[i] = 0XFF; + } + for (uint32_t i = 0; i < ns; i++) { + if (i % (ns / 32) == 0) { + writeMsg(pr, "."); + } + if (!dev->writeSector(sector + i, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + if (i == 0) { + memset(secBuf, 0, BYTES_PER_SECTOR); + } + } + writeMsg(pr, "\r\n"); + // Write cluster two, bitmap. + sector = partitionOffset + clusterHeapOffset; + bitmapSize = (clusterCount + 7) / 8; + ns = (bitmapSize + BYTES_PER_SECTOR - 1) / BYTES_PER_SECTOR; + if (ns > sectorsPerCluster) { + DBG_FAIL_MACRO; + goto fail; + } + memset(secBuf, 0, BYTES_PER_SECTOR); + // Allocate clusters for bitmap, upcase, and root. + secBuf[0] = 0X7; + for (uint32_t i = 0; i < ns; i++) { + if (!dev->writeSector(sector + i, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + if (i == 0) { + secBuf[0] = 0; + } + } + // Write cluster three, upcase table. + writeMsg(pr, "Writing upcase table\r\n"); + if (!writeUpcase(partitionOffset + clusterHeapOffset + sectorsPerCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_upcaseSize > BYTES_PER_SECTOR * sectorsPerCluster) { + DBG_FAIL_MACRO; + goto fail; + } + // Initialize first sector of root. + writeMsg(pr, "Writing root\r\n"); + ns = sectorsPerCluster; + sector = partitionOffset + clusterHeapOffset + 2 * sectorsPerCluster; + memset(secBuf, 0, BYTES_PER_SECTOR); + + // Unused Label entry. + label = reinterpret_cast(secBuf); + label->type = EXFAT_TYPE_LABEL & 0X7F; + + // bitmap directory entry. + dbm = reinterpret_cast(secBuf + 32); + dbm->type = EXFAT_TYPE_BITMAP; + setLe32(dbm->firstCluster, BITMAP_CLUSTER); + setLe64(dbm->size, bitmapSize); + + // upcase directory entry. + dup = reinterpret_cast(secBuf + 64); + dup->type = EXFAT_TYPE_UPCASE; + setLe32(dup->checksum, m_upcaseChecksum); + setLe32(dup->firstCluster, UPCASE_CLUSTER); + setLe64(dup->size, m_upcaseSize); + + // Write root, cluster four. + for (uint32_t i = 0; i < ns; i++) { + if (!dev->writeSector(sector + i, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + if (i == 0) { + memset(secBuf, 0, BYTES_PER_SECTOR); + } + } + writeMsg(pr, "Format done\r\n"); + return true; + +fail: + writeMsg(pr, "Format failed\r\n"); + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::syncUpcase() { + uint16_t index = m_upcaseSize & SECTOR_MASK; + if (!index) { + return true; + } + for (size_t i = index; i < BYTES_PER_SECTOR; i++) { + m_secBuf[i] = 0; + } + return m_dev->writeSector(m_upcaseSector, m_secBuf); +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::writeUpcaseByte(uint8_t b) { + uint16_t index = m_upcaseSize & SECTOR_MASK; + m_secBuf[index] = b; + m_upcaseChecksum = exFatChecksum(m_upcaseChecksum, b); + m_upcaseSize++; + if (index == SECTOR_MASK) { + return m_dev->writeSector(m_upcaseSector++, m_secBuf); + } + return true; +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::writeUpcaseUnicode(uint16_t unicode) { + return writeUpcaseByte(unicode) && writeUpcaseByte(unicode >> 8); +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::writeUpcase(Sector_t sector) { + uint32_t n; + uint32_t ns; + uint32_t ch = 0; + uint16_t uc; + + m_upcaseSize = 0; + m_upcaseChecksum = 0; + m_upcaseSector = sector; + + while (ch < 0X10000) { + uc = toUpcase(ch); + if (uc != ch) { + if (!writeUpcaseUnicode(uc)) { + DBG_FAIL_MACRO; + goto fail; + } + ch++; + } else { + for (n = ch + 1; n < 0X10000 && n == toUpcase(n); n++) { + } + ns = n - ch; + if (ns >= MINIMUM_UPCASE_SKIP) { + if (!writeUpcaseUnicode(0XFFFF) || !writeUpcaseUnicode(ns)) { + DBG_FAIL_MACRO; + goto fail; + } + ch = n; + } else { + while (ch < n) { + if (!writeUpcaseUnicode(ch++)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + } + } + if (!syncUpcase()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} diff --git a/third_party/sdfat/src/ExFatLib/ExFatFormatter.h b/third_party/sdfat/src/ExFatLib/ExFatFormatter.h new file mode 100644 index 00000000..ce9ca23c --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatFormatter.h @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "../common/FsBlockDevice.h" +/** + * \class ExFatFormatter + * \brief Format an exFAT volume. + */ +class ExFatFormatter { + public: + /** Constructor. */ + ExFatFormatter() = default; // cppcheck-suppress uninitMemberVar + /** + * Format an exFAT volume. + * + * \param[in] dev Block device for volume. + * \param[in] secBuf buffer for writing to volume. + * \param[in] pr Print device for progress output. + * + * \return true for success or false for failure. + */ + bool format(FsBlockDevice* dev, uint8_t* secBuf, print_t* pr = nullptr); + + private: + bool syncUpcase(); + bool writeUpcase(Sector_t sector); + bool writeUpcaseByte(uint8_t b); + bool writeUpcaseUnicode(uint16_t unicode); + Sector_t m_upcaseSector; + uint32_t m_upcaseChecksum; + uint32_t m_upcaseSize; + FsBlockDevice* m_dev; + uint8_t* m_secBuf; +}; diff --git a/third_party/sdfat/src/ExFatLib/ExFatLib.h b/third_party/sdfat/src/ExFatLib/ExFatLib.h new file mode 100644 index 00000000..bf8d306d --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatLib.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "ExFatFormatter.h" +#include "ExFatVolume.h" diff --git a/third_party/sdfat/src/ExFatLib/ExFatName.cpp b/third_party/sdfat/src/ExFatLib/ExFatName.cpp new file mode 100644 index 00000000..6e805e0a --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatName.cpp @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatName.cpp" +#include "../common/DebugMacros.h" +#include "../common/FsUtf.h" +#include "../common/upcase.h" +#include "ExFatLib.h" +//------------------------------------------------------------------------------ +static char toUpper(char c) { return 'a' <= c && c <= 'z' ? c - 'a' + 'A' : c; } +//------------------------------------------------------------------------------ +inline uint16_t exFatHash(char c, uint16_t hash) { + uint8_t u = toUpper(c); + hash = ((hash << 15) | (hash >> 1)) + u; + hash = ((hash << 15) | (hash >> 1)); + return hash; +} +//------------------------------------------------------------------------------ +inline uint16_t exFatHash(uint16_t u, uint16_t hash) { + uint16_t c = toUpcase(u); + hash = ((hash << 15) | (hash >> 1)) + (c & 0XFF); + hash = ((hash << 15) | (hash >> 1)) + (c >> 8); + return hash; +} +//------------------------------------------------------------------------------ +bool ExFatFile::cmpName(const DirName_t* dirName, ExName_t* fname) { + for (uint8_t i = 0; i < 15; i++) { + uint16_t u = getLe16(dirName->unicode + 2 * i); + if (fname->atEnd()) { + return u == 0; + } +#if USE_UTF8_LONG_NAMES + uint16_t cp = fname->get16(); + if (toUpcase(cp) != toUpcase(u)) { + return false; + } +#else // USE_UTF8_LONG_NAMES + char c = fname->getch(); + if (u >= 0x7F || toUpper(c) != toUpper(u)) { + return false; + } +#endif // USE_UTF8_LONG_NAMES + } + return true; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::getName7(char* name, size_t count) { + const DirName_t* dn; + size_t n = 0; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t is = 2; is <= m_setCount; is++) { + dn = reinterpret_cast(dirCache(is, FsCache::CACHE_FOR_READ)); + if (!dn || dn->type != EXFAT_TYPE_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t in = 0; in < 15; in++) { + uint16_t c = getLe16(dn->unicode + 2 * in); + if (c == 0) { + goto done; + } + if ((n + 1) >= count) { + DBG_FAIL_MACRO; + goto fail; + } + name[n++] = c < 0X7F ? c : '?'; + } + } +done: + name[n] = 0; + return n; + +fail: + *name = 0; + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::getName8(char* name, size_t count) { + const char* end = name + count; + char* str = name; + char* ptr; + const DirName_t* dn; + uint16_t hs = 0; + uint32_t cp; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t is = 2; is <= m_setCount; is++) { + dn = reinterpret_cast(dirCache(is, FsCache::CACHE_FOR_READ)); + if (!dn || dn->type != EXFAT_TYPE_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t in = 0; in < 15; in++) { + uint16_t c = getLe16(dn->unicode + 2 * in); + if (hs) { + if (!FsUtf::isLowSurrogate(c)) { + DBG_FAIL_MACRO; + goto fail; + } + cp = FsUtf::u16ToCp(hs, c); + hs = 0; + } else if (!FsUtf::isSurrogate(c)) { + if (c == 0) { + goto done; + } + cp = c; + } else if (FsUtf::isHighSurrogate(c)) { + hs = c; + continue; + } else { + DBG_FAIL_MACRO; + goto fail; + } + // Save space for zero byte. + ptr = FsUtf::cpToMb(cp, str, end - 1); + if (!ptr) { + DBG_FAIL_MACRO; + goto fail; + } + str = ptr; + } + } +done: + *str = '\0'; + return str - name; + +fail: + *name = 0; + return 0; +} +//------------------------------------------------------------------------------ +bool ExFatFile::hashName(ExName_t* fname) { + uint16_t hash = 0; + fname->reset(); +#if USE_UTF8_LONG_NAMES + fname->nameLength = 0; + while (!fname->atEnd()) { + uint16_t u = fname->get16(); + if (u == 0XFFFF) { + DBG_FAIL_MACRO; + goto fail; + } + hash = exFatHash(u, hash); + fname->nameLength++; + } +#else // USE_UTF8_LONG_NAMES + while (!fname->atEnd()) { + // Convert to byte for smaller exFatHash. + char c = fname->getch(); + hash = exFatHash(c, hash); + } + fname->nameLength = fname->end - fname->begin; +#endif // USE_UTF8_LONG_NAMES + fname->nameHash = hash; + if (!fname->nameLength || fname->nameLength > EXFAT_MAX_NAME_LENGTH) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} diff --git a/third_party/sdfat/src/ExFatLib/ExFatPartition.cpp b/third_party/sdfat/src/ExFatLib/ExFatPartition.cpp new file mode 100644 index 00000000..619af908 --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatPartition.cpp @@ -0,0 +1,332 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatPartition.cpp" +#include "../common/DebugMacros.h" +#include "ExFatLib.h" +//------------------------------------------------------------------------------ +// return 0 if error, 1 if no space, else start cluster. +Cluster_t ExFatPartition::bitmapFind(Cluster_t cluster, uint32_t count) { + Cluster_t start = cluster ? cluster - 2 : m_bitmapStart; + if (start >= m_clusterCount) { + start = 0; + } + Cluster_t endAlloc = start; + Cluster_t bgnAlloc = start; + uint16_t sectorSize = 1 << m_bytesPerSectorShift; + size_t i = (start >> 3) & (sectorSize - 1); + const uint8_t* cache; + uint8_t mask = 1 << (start & 7); + while (true) { + Sector_t sector = + m_clusterHeapStartSector + (endAlloc >> (m_bytesPerSectorShift + 3)); + cache = bitmapCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + return 0; + } + for (; i < sectorSize; i++) { + for (; mask; mask <<= 1) { + endAlloc++; + if (!(mask & cache[i])) { + if ((endAlloc - bgnAlloc) == count) { + if (cluster == 0 && count == 1) { + // Start at found sector. bitmapModify may increase this. + m_bitmapStart = bgnAlloc; + } + return bgnAlloc + 2; + } + } else { + bgnAlloc = endAlloc; + } + if (endAlloc == start) { + return 1; + } + if (endAlloc >= m_clusterCount) { + endAlloc = bgnAlloc = 0; + i = sectorSize; + break; + } + } + mask = 1; + } + i = 0; + } + return 0; +} +//------------------------------------------------------------------------------ +bool ExFatPartition::bitmapModify(Cluster_t cluster, uint32_t count, + bool value) { + Sector_t sector; + Cluster_t start = cluster - 2; + size_t i; + uint8_t* cache; + uint8_t mask; + cluster -= 2; + if ((start + count) > m_clusterCount) { + DBG_FAIL_MACRO; + goto fail; + } + if (value) { + if (start <= m_bitmapStart && m_bitmapStart < (start + count)) { + m_bitmapStart = (start + count) < m_clusterCount ? start + count : 0; + } + } else { + if (start < m_bitmapStart) { + m_bitmapStart = start; + } + } + mask = 1 << (start & 7); + sector = m_clusterHeapStartSector + (start >> (m_bytesPerSectorShift + 3)); + i = (start >> 3) & m_sectorMask; + while (true) { + cache = bitmapCachePrepare(sector++, FsCache::CACHE_FOR_WRITE); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + for (; i < m_bytesPerSector; i++) { + for (; mask; mask <<= 1) { + if (value == static_cast(cache[i] & mask)) { + DBG_FAIL_MACRO; + goto fail; + } + cache[i] ^= mask; + if (--count == 0) { + return true; + } + } + mask = 1; + } + i = 0; + } + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t ExFatPartition::chainSize(Cluster_t cluster) { + uint32_t n = 0; + int8_t status; + do { + status = fatGet(cluster, &cluster); + if (status < 0) return 0; + n++; + } while (status); + return n; +} +//------------------------------------------------------------------------------ +uint8_t* ExFatPartition::dirCache(const DirPos_t* pos, uint8_t options) { + Sector_t sector = clusterStartSector(pos->cluster); + sector += (m_clusterMask & pos->position) >> m_bytesPerSectorShift; + uint8_t* cache = dataCachePrepare(sector, options); + return cache ? cache + (pos->position & m_sectorMask) : nullptr; +} +//------------------------------------------------------------------------------ +// return -1 error, 0 EOC, 1 OK +int8_t ExFatPartition::dirSeek(DirPos_t* pos, uint32_t offset) { + int8_t status; + uint32_t tmp = (m_clusterMask & pos->position) + offset; + pos->position += offset; + tmp >>= bytesPerClusterShift(); + while (tmp--) { + if (pos->isContiguous) { + pos->cluster++; + } else { + status = fatGet(pos->cluster, &pos->cluster); + if (status != 1) { + return status; + } + } + } + return 1; +} +//------------------------------------------------------------------------------ +// return -1 error, 0 EOC, 1 OK +int8_t ExFatPartition::fatGet(Cluster_t cluster, Cluster_t* value) { + const uint8_t* cache; + Cluster_t next; + Sector_t sector; + + if (cluster > (m_clusterCount + 1)) { + DBG_FAIL_MACRO; + return -1; + } + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + + cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + return -1; + } + next = getLe32(cache + ((cluster << 2) & m_sectorMask)); + if (next == EXFAT_EOC) { + return 0; + } + *value = next; + return 1; +} +//------------------------------------------------------------------------------ +bool ExFatPartition::fatPut(Cluster_t cluster, Cluster_t value) { + Sector_t sector; + uint8_t* cache; + if (cluster < 2 || cluster > (m_clusterCount + 1)) { + DBG_FAIL_MACRO; + goto fail; + } + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + cache = dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + setLe32(cache + ((cluster << 2) & m_sectorMask), value); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatPartition::freeChain(Cluster_t cluster) { + Cluster_t next; + Cluster_t start = cluster; + int8_t status; + do { + status = fatGet(cluster, &next); + if (status < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (!fatPut(cluster, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + if (status == 0 || (cluster + 1) != next) { + if (!bitmapModify(start, cluster - start + 1, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + start = next; + } + cluster = next; + } while (status); + + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +Cluster_t ExFatPartition::freeClusterCount() { + Cluster_t nc = 0; + Sector_t sector = m_clusterHeapStartSector; + Cluster_t usedCount = 0; + const uint8_t* cache; + + while (true) { + cache = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ); + if (!cache) { + return -1; + } + for (size_t i = 0; i < m_bytesPerSector; i++) { + if (cache[i] == 0XFF) { + usedCount += 8; + } else if (cache[i]) { + for (uint8_t mask = 1; mask; mask <<= 1) { + if ((mask & cache[i])) { + usedCount++; + } + } + } + nc += 8; + if (nc >= m_clusterCount) { + return m_clusterCount - usedCount; + } + } + } +} +//------------------------------------------------------------------------------ +bool ExFatPartition::init(FsBlockDevice* dev, uint8_t part, + Sector_t startSector) { + pbs_t* pbs; + const BpbExFat_t* bpb; + const MbrSector_t* mbr; + m_fatType = 0; + m_blockDev = dev; + cacheInit(m_blockDev); + // if part == 0 assume super floppy with FAT boot sector in sector zero + // if part > 0 assume mbr volume with partition table + if (part) { + if (part > 4) { + DBG_FAIL_MACRO; + goto fail; + } + mbr = reinterpret_cast( + dataCachePrepare(0, FsCache::CACHE_FOR_READ)); + if (!mbr) { + DBG_FAIL_MACRO; + goto fail; + } + const MbrPart_t* mp = mbr->part + part - 1; + if (mp->type == 0 || (mp->boot != 0 && mp->boot != 0X80)) { + DBG_FAIL_MACRO; + goto fail; + } + startSector = getLe32(mp->startSector); + } + pbs = reinterpret_cast( + dataCachePrepare(startSector, FsCache::CACHE_FOR_READ)); + if (!pbs) { + DBG_FAIL_MACRO; + goto fail; + } + if (strncmp(pbs->oemName, "EXFAT", 5)) { + DBG_FAIL_MACRO; + goto fail; + } + bpb = reinterpret_cast(pbs->bpb); + if (bpb->bytesPerSectorShift != m_bytesPerSectorShift) { + DBG_FAIL_MACRO; + goto fail; + } + m_fatStartSector = startSector + getLe32(bpb->fatOffset); + m_fatLength = getLe32(bpb->fatLength); + m_clusterHeapStartSector = startSector + getLe32(bpb->clusterHeapOffset); + m_clusterCount = getLe32(bpb->clusterCount); + m_rootDirectoryCluster = getLe32(bpb->rootDirectoryCluster); + m_sectorsPerClusterShift = bpb->sectorsPerClusterShift; + m_bytesPerCluster = 1UL << (m_bytesPerSectorShift + m_sectorsPerClusterShift); + m_clusterMask = m_bytesPerCluster - 1; + // Set m_bitmapStart to first free cluster. + m_bitmapStart = 0; + bitmapFind(0, 1); + m_fatType = FAT_TYPE_EXFAT; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t ExFatPartition::rootLength() { + uint32_t nc = chainSize(m_rootDirectoryCluster); + return nc << bytesPerClusterShift(); +} diff --git a/third_party/sdfat/src/ExFatLib/ExFatPartition.h b/third_party/sdfat/src/ExFatLib/ExFatPartition.h new file mode 100644 index 00000000..149a4340 --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatPartition.h @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief ExFatPartition include file. + */ +#include "../common/FsBlockDevice.h" +#include "../common/FsCache.h" +#include "../common/FsStructs.h" +#include "../common/SysCall.h" +/** Set EXFAT_READ_ONLY non-zero for read only */ +#ifndef EXFAT_READ_ONLY +#define EXFAT_READ_ONLY 0 +#endif // EXFAT_READ_ONLY +/** Type for exFAT partition */ +const uint8_t FAT_TYPE_EXFAT = 64; + +class ExFatFile; +//------------------------------------------------------------------------------ +/** + * \struct DirPos_t + * \brief Internal type for position in directory file. + */ +struct DirPos_t { + /** current cluster */ + Cluster_t cluster; + /** offset */ + uint32_t position; + /** directory is contiguous */ + bool isContiguous; +}; +//============================================================================== +/** + * \class ExFatPartition + * \brief Access exFat partitions on raw file devices. + */ +class ExFatPartition { + public: + ExFatPartition() = default; // cppcheck-suppress uninitMemberVar + /** \return the number of bytes in a cluster. */ + uint32_t bytesPerCluster() const { return m_bytesPerCluster; } + /** \return the power of two for bytesPerCluster. */ + uint8_t bytesPerClusterShift() const { + return m_bytesPerSectorShift + m_sectorsPerClusterShift; + } + /** \return the number of bytes in a sector. */ + uint16_t bytesPerSector() const { return m_bytesPerSector; } + /** \return the power of two for bytesPerSector. */ + uint8_t bytesPerSectorShift() const { return m_bytesPerSectorShift; } + + /** Clear the cache and returns a pointer to the cache. Not for normal apps. + * \return A pointer to the cache buffer or zero if an error occurs. + */ + uint8_t* cacheClear() { return m_dataCache.clear(); } + /** \return the cluster count for the partition. */ + Cluster_t clusterCount() const { return m_clusterCount; } + /** \return the cluster heap start sector. */ + Cluster_t clusterHeapStartSector() const { return m_clusterHeapStartSector; } + /** End access to volume + * \return pointer to sector size buffer for format. + */ + uint8_t* end() { + m_fatType = 0; + return cacheClear(); + } + /** \return The number of File Allocation Tables. */ + uint8_t fatCount() const { return 1; } + /** \return the FAT length in sectors */ + uint32_t fatLength() const { return m_fatLength; } + /** \return the FAT start sector number. */ + Sector_t fatStartSector() const { return m_fatStartSector; } + /** \return Type FAT_TYPE_EXFAT for exFAT partition or zero for error. */ + uint8_t fatType() const { return m_fatType; } + /** \return free cluster count or -1 if an error occurs. */ + Cluster_t freeClusterCount(); + /** Initialize a exFAT partition. + * \param[in] dev The blockDevice for the partition. + * \param[in] part The partition to be used. Legal values for \a part are + * 1-4 to use the corresponding partition on a device formatted with + * a MBR, Master Boot Record, or zero if the device is formatted as + * a super floppy with the FAT boot sector in sector startSector. + * \param[in] startSector location of volume if part is zero. + * + * \return true for success or false for failure. + */ + bool init(FsBlockDevice* dev, uint8_t part, Sector_t startSector = 0); + /** + * Check for device busy. + * + * \return true if busy else false. + */ + bool isBusy() { return m_blockDev->isBusy(); } + /** \return the root directory start cluster number. */ + Cluster_t rootDirectoryCluster() const { return m_rootDirectoryCluster; } + /** \return the root directory length. */ + uint32_t rootLength(); + /** \return the number of sectors in a cluster. */ + Sector_t sectorsPerCluster() const { return 1UL << m_sectorsPerClusterShift; } + /** \return the power of two for sectors per cluster. */ + uint8_t sectorsPerClusterShift() const { return m_sectorsPerClusterShift; } + //---------------------------------------------------------------------------- +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void checkUpcase(print_t* pr); + bool printDir(print_t* pr, ExFatFile* file); + void dmpBitmap(print_t* pr); + void dmpCluster(print_t* pr, Cluster_t cluster, uint32_t offset, + uint32_t count); + void dmpFat(print_t* pr, uint32_t start, uint32_t count); + void dmpSector(print_t* pr, Sector_t sector, uint8_t w = 16); + bool printVolInfo(print_t* pr); + void printFat(print_t* pr); + void printUpcase(print_t* pr); +#endif // DOXYGEN_SHOULD_SKIP_THIS + //---------------------------------------------------------------------------- + private: + /** ExFatFile allowed access to private members. */ + friend class ExFatFile; + uint32_t bitmapFind(Cluster_t cluster, uint32_t count); + bool bitmapModify(Cluster_t cluster, uint32_t count, bool value); + //---------------------------------------------------------------------------- + // Cache functions. + uint8_t* bitmapCachePrepare(Sector_t sector, uint8_t option) { +#if USE_EXFAT_BITMAP_CACHE + return m_bitmapCache.prepare(sector, option); +#else // USE_EXFAT_BITMAP_CACHE + return m_dataCache.prepare(sector, option); +#endif // USE_EXFAT_BITMAP_CACHE + } + void cacheInit(FsBlockDevice* dev) { +#if USE_EXFAT_BITMAP_CACHE + m_bitmapCache.init(dev); +#endif // USE_EXFAT_BITMAP_CACHE + m_dataCache.init(dev); + } + bool cacheSync() { +#if USE_EXFAT_BITMAP_CACHE + return m_bitmapCache.sync() && m_dataCache.sync() && syncDevice(); +#else // USE_EXFAT_BITMAP_CACHE + return m_dataCache.sync() && syncDevice(); +#endif // USE_EXFAT_BITMAP_CACHE + } + void dataCacheDirty() { m_dataCache.dirty(); } + void dataCacheInvalidate() { m_dataCache.invalidate(); } + uint8_t* dataCachePrepare(Sector_t sector, uint8_t option) { + return m_dataCache.prepare(sector, option); + } + Sector_t dataCacheSector() { return m_dataCache.sector(); } + bool dataCacheSync() { return m_dataCache.sync(); } + //---------------------------------------------------------------------------- + uint32_t clusterMask() const { return m_clusterMask; } + Sector_t clusterStartSector(Cluster_t cluster) { + return m_clusterHeapStartSector + + ((cluster - 2) << m_sectorsPerClusterShift); + } + uint8_t* dirCache(const DirPos_t* pos, uint8_t options); + int8_t dirSeek(DirPos_t* pos, uint32_t offset); + int8_t fatGet(Cluster_t cluster, Cluster_t* value); + bool fatPut(Cluster_t cluster, Cluster_t value); + Cluster_t chainSize(Cluster_t cluster); + bool freeChain(Cluster_t cluster); + uint16_t sectorMask() const { return m_sectorMask; } + bool syncDevice() { return m_blockDev->syncDevice(); } + bool cacheSafeRead(Sector_t sector, uint8_t* dst) { + return m_dataCache.cacheSafeRead(sector, dst); + } + bool cacheSafeWrite(Sector_t sector, const uint8_t* src) { + return m_dataCache.cacheSafeWrite(sector, src); + } + bool cacheSafeRead(Sector_t sector, uint8_t* dst, size_t count) { + return m_dataCache.cacheSafeRead(sector, dst, count); + } + bool cacheSafeWrite(Sector_t sector, const uint8_t* src, size_t count) { + return m_dataCache.cacheSafeWrite(sector, src, count); + } + bool readSector(Sector_t sector, uint8_t* dst) { + return m_blockDev->readSector(sector, dst); + } + bool writeSector(Sector_t sector, const uint8_t* src) { + return m_blockDev->writeSector(sector, src); + } + //---------------------------------------------------------------------------- + static const uint8_t m_bytesPerSectorShift = 9; + static const uint16_t m_bytesPerSector = 1 << m_bytesPerSectorShift; + static const uint16_t m_sectorMask = m_bytesPerSector - 1; + //---------------------------------------------------------------------------- +#if USE_EXFAT_BITMAP_CACHE + FsCache m_bitmapCache; +#endif // USE_EXFAT_BITMAP_CACHE + FsCache m_dataCache; + Sector_t m_bitmapStart; + Sector_t m_fatStartSector; + uint32_t m_fatLength; + Sector_t m_clusterHeapStartSector; + Cluster_t m_clusterCount; + Cluster_t m_rootDirectoryCluster; + uint32_t m_clusterMask; + uint32_t m_bytesPerCluster; + FsBlockDevice* m_blockDev; + uint8_t m_fatType = 0; + uint8_t m_sectorsPerClusterShift; +}; diff --git a/third_party/sdfat/src/ExFatLib/ExFatVolume.cpp b/third_party/sdfat/src/ExFatLib/ExFatVolume.cpp new file mode 100644 index 00000000..d110b9d0 --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatVolume.cpp @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatVolume.cpp" +#include "../common/DebugMacros.h" +#include "ExFatLib.h" +ExFatVolume* ExFatVolume::m_cwv = nullptr; +//----------------------------------------------------------------------------- +bool ExFatVolume::chdir(const char* path) { + ExFatFile dir; + if (!dir.open(vwd(), path, O_RDONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!dir.isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + m_vwd.copy(&dir); + return true; + +fail: + return false; +} diff --git a/third_party/sdfat/src/ExFatLib/ExFatVolume.h b/third_party/sdfat/src/ExFatLib/ExFatVolume.h new file mode 100644 index 00000000..c9205561 --- /dev/null +++ b/third_party/sdfat/src/ExFatLib/ExFatVolume.h @@ -0,0 +1,364 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "ExFatFile.h" +//============================================================================== +/** + * \class ExFatVolume + * \brief exFAT volume. + */ +class ExFatVolume : public ExFatPartition { + public: + ExFatVolume() {} + //---------------------------------------------------------------------------- + /** Get file's user settable attributes. + * \param[in] path path to file. + * \return user settable file attributes for success else -1. + */ + int attrib(const char* path) { + ExFatFile tmpFile; + return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib() : -1; + } + //---------------------------------------------------------------------------- + /** Set file's user settable attributes. + * \param[in] path path to file. + * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY, + * FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE. + * + * \return true for success or false for failure. + */ + bool attrib(const char* path, uint8_t bits) { + ExFatFile tmpFile; + return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib(bits) : false; + } + //---------------------------------------------------------------------------- + /** + * Initialize an FatVolume object. + * \param[in] dev Device block driver. + * \param[in] setCwv Set current working volume if true. + * \param[in] part Partition to initialize. + * \param[in] startSector Start sector of volume if part is zero. + * \return true for success or false for failure. + */ + bool begin(FsBlockDevice* dev, bool setCwv = true, uint8_t part = 1, + uint32_t startSector = 0) { + if (!init(dev, part, startSector)) { + return false; + } + if (!chdir()) { + return false; + } + if (setCwv || !m_cwv) { + m_cwv = this; + } + return true; + } + //---------------------------------------------------------------------------- + /** + * Set volume working directory to root. + * \return true for success or false for failure. + */ + bool chdir() { + m_vwd.close(); + return m_vwd.openRoot(this); + } + //---------------------------------------------------------------------------- + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const char* path); + //---------------------------------------------------------------------------- + /** Change global working volume to this volume. */ + void chvol() { m_cwv = this; } + //---------------------------------------------------------------------------- + /** + * Test for the existence of a file. + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + ExFatFile tmp; + return tmp.open(this, path, O_RDONLY); + } + //---------------------------------------------------------------------------- + /** List the directory contents of the root directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags = 0) { return m_vwd.ls(pr, flags); } + //---------------------------------------------------------------------------- + /** List the contents of a directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, const char* path, uint8_t flags) { + ExFatFile dir; + return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags); + } + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const char* path, bool pFlag = true) { + ExFatFile sub; + return sub.mkdir(vwd(), path, pFlag); + } + //---------------------------------------------------------------------------- + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a ExFile object. + */ + ExFile open(const char* path, oflag_t oflag = O_RDONLY) { + ExFile tmpFile; + tmpFile.open(this, path, oflag); + return tmpFile; + } + //---------------------------------------------------------------------------- + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const char* path) { + ExFatFile tmp; + return tmp.open(this, path, O_WRONLY) && tmp.remove(); + } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const char* oldPath, const char* newPath) { + ExFatFile file; + return file.open(vwd(), oldPath, O_RDONLY) && file.rename(vwd(), newPath); + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const char* path) { + ExFatFile sub; + return sub.open(this, path, O_RDONLY) && sub.rmdir(); + } + //---------------------------------------------------------------------------- + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const char* path, uint64_t length) { + ExFatFile file; + if (!file.open(this, path, O_WRONLY)) { + return false; + } + return file.truncate(length); + } +#if ENABLE_ARDUINO_SERIAL + //---------------------------------------------------------------------------- + /** List the directory contents of the root directory to Serial. + * + * \return true for success or false for failure. + */ + bool ls() { return ls(&Serial); } + //---------------------------------------------------------------------------- + /** List the directory contents of the volume root to Serial. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags) { return ls(&Serial, flags); } + //---------------------------------------------------------------------------- + /** List the directory contents of a directory to Serial. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(const char* path, uint8_t flags = 0) { + return ls(&Serial, path, flags); + } +#endif // ENABLE_ARDUINO_SERIAL +#if ENABLE_ARDUINO_STRING + //---------------------------------------------------------------------------- + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const String& path) { return chdir(path.c_str()); } + //---------------------------------------------------------------------------- + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const String& path) { return exists(path.c_str()); } + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const String& path, bool pFlag = true) { + return mkdir(path.c_str(), pFlag); + } + //---------------------------------------------------------------------------- + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open oflag flags. + * \return a ExFile object. + */ + ExFile open(const String& path, oflag_t oflag = O_RDONLY) { + return open(path.c_str(), oflag); + } + //---------------------------------------------------------------------------- + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const String& path) { return remove(path.c_str()); } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const String& oldPath, const String& newPath) { + return rename(oldPath.c_str(), newPath.c_str()); + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const String& path) { return rmdir(path.c_str()); } + //---------------------------------------------------------------------------- + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const String& path, uint64_t length) { + return truncate(path.c_str(), length); + } +#endif // ENABLE_ARDUINO_STRING + + private: + friend ExFatFile; + static ExFatVolume* cwv() { return m_cwv; } + ExFatFile* vwd() { return &m_vwd; } + static ExFatVolume* m_cwv; + ExFatFile m_vwd; +}; diff --git a/third_party/sdfat/src/FatLib/FatDbg.cpp b/third_party/sdfat/src/FatLib/FatDbg.cpp new file mode 100644 index 00000000..ea60088a --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatDbg.cpp @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FatLib.h" +#if ENABLE_ARDUINO_FEATURES +#ifndef DOXYGEN_SHOULD_SKIP_THIS +//------------------------------------------------------------------------------ +static uint16_t getLfnChar(const DirLfn_t* ldir, uint8_t i) { + if (i < 5) { + return getLe16(ldir->unicode1 + 2 * i); + } else if (i < 11) { + return getLe16(ldir->unicode2 + 2 * (i - 5)); + } else if (i < 13) { + return getLe16(ldir->unicode3 + 2 * (i - 11)); + } + return 0; +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t h) { + if (h < 16) { + pr->write('0'); + } + pr->print(h, HEX); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t w, uint16_t h) { + char buf[5]; + char* ptr = buf + sizeof(buf); + *--ptr = 0; + for (uint8_t i = 0; i < w; i++) { + char c = h & 0XF; + *--ptr = c < 10 ? c + '0' : c + 'A' - 10; + h >>= 4; + } + pr->write(ptr); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint16_t val) { + bool space = true; + for (uint8_t i = 0; i < 4; i++) { + uint8_t h = (val >> (12 - 4 * i)) & 15; + if (h || i == 3) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint32_t val) { + bool space = true; + for (uint8_t i = 0; i < 8; i++) { + uint8_t h = (val >> (28 - 4 * i)) & 15; + if (h || i == 7) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +template +static void printHexLn(print_t* pr, Uint val) { + printHex(pr, val); + pr->println(); +} +//------------------------------------------------------------------------------ +static bool printFatDir(print_t* pr, DirFat_t* dir) { + const DirLfn_t* ldir = reinterpret_cast(dir); + if (!dir->name[0]) { + pr->println(F("Unused")); + return false; + } else if (dir->name[0] == FAT_NAME_DELETED) { + pr->println(F("Deleted")); + } else if (isFatFileOrSubdir(dir)) { + pr->print(F("SFN: ")); + for (uint8_t i = 0; i < 11; i++) { + printHex(pr, dir->name[i]); + pr->write(' '); + } + pr->write(' '); + pr->write(dir->name, 11); + pr->println(); + pr->print(F("attributes: 0X")); + printHexLn(pr, dir->attributes); + pr->print(F("caseFlags: 0X")); + printHexLn(pr, dir->caseFlags); + Cluster_t fc = + (static_cast(getLe16(dir->firstClusterHigh)) << 16) | + getLe16(dir->firstClusterLow); + pr->print(F("firstCluster: ")); + pr->println(fc, HEX); + pr->print(F("fileSize: ")); + pr->println(getLe32(dir->fileSize)); + } else if (isFatLongName(dir)) { + pr->print(F("LFN: ")); + for (uint8_t i = 0; i < 13; i++) { + uint16_t c = getLfnChar(ldir, i); + if (15 < c && c < 128) { + pr->print(static_cast(c)); + } else { + pr->print("0X"); + pr->print(c, HEX); + } + pr->print(' '); + } + pr->println(); + pr->print(F("order: 0X")); + pr->println(ldir->order, HEX); + pr->print(F("attributes: 0X")); + pr->println(ldir->attributes, HEX); + pr->print(F("checksum: 0X")); + pr->println(ldir->checksum, HEX); + } else { + pr->println(F("Other")); + } + pr->println(); + return true; +} +//------------------------------------------------------------------------------ +void FatFile::dmpFile(print_t* pr, uint32_t pos, size_t n) { + char text[17]; + text[16] = 0; + if (n >= 0XFFF0) { + n = 0XFFF0; + } + if (!seekSet(pos)) { + return; + } + for (size_t i = 0; i <= n; i++) { + if ((i & 15) == 0) { + if (i) { + pr->write(' '); + pr->write(text); + if (i == n) { + break; + } + } + pr->write('\r'); + pr->write('\n'); + if (i >= n) { + break; + } + printHex(pr, 4, i); + pr->write(' '); + } + int16_t h = read(); + if (h < 0) { + break; + } + pr->write(' '); + printHex(pr, 2, h); + text[i & 15] = ' ' <= h && h < 0X7F ? h : '.'; + } + pr->write('\r'); + pr->write('\n'); +} +//------------------------------------------------------------------------------ +bool FatPartition::dmpDirSector(print_t* pr, Sector_t sector) { + DirFat_t dir[16]; + if (!cacheSafeRead(sector, reinterpret_cast(dir))) { + pr->println(F("dmpDir failed")); + return false; + } + for (uint8_t i = 0; i < 16; i++) { + if (!printFatDir(pr, dir + i)) { + return false; + } + } + return true; +} +//------------------------------------------------------------------------------ +bool FatPartition::dmpRootDir(print_t* pr, uint32_t n) { + Sector_t sector; + if (fatType() == 16) { + sector = rootDirStart(); + } else if (fatType() == 32) { + sector = clusterStartSector(rootDirStart()); + } else { + pr->println(F("dmpRootDir failed")); + return false; + } + return dmpDirSector(pr, sector + n); +} +//------------------------------------------------------------------------------ +void FatPartition::dmpSector(print_t* pr, Sector_t sector, uint8_t bits) { + uint8_t data[FatPartition::m_bytesPerSector]; + if (!cacheSafeRead(sector, data)) { + pr->println(F("dmpSector failed")); + return; + } + for (uint16_t i = 0; i < m_bytesPerSector;) { + if (i % 32 == 0) { + if (i) { + pr->println(); + } + printHex(pr, i); + } + pr->write(' '); + if (bits == 32) { + printHex(pr, *reinterpret_cast(data + i)); + i += 4; + } else if (bits == 16) { + printHex(pr, *reinterpret_cast(data + i)); + i += 2; + } else { + printHex(pr, data[i++]); + } + } + pr->println(); +} +//------------------------------------------------------------------------------ +void FatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) { + uint16_t nf = fatType() == 16 ? 256 : fatType() == 32 ? 128 : 0; + if (nf == 0) { + pr->println(F("Invalid fatType")); + return; + } + pr->println(F("FAT:")); + Sector_t sector = m_fatStartSector + start; + Cluster_t cluster = nf * start; + for (uint32_t i = 0; i < count; i++) { + const uint8_t* pc = fatCachePrepare(sector + i, FsCache::CACHE_FOR_READ); + if (!pc) { + pr->println(F("cache read failed")); + return; + } + for (size_t k = 0; k < nf; k++) { + if (0 == cluster % 8) { + if (k) { + pr->println(); + } + printHex(pr, cluster); + } + cluster++; + pr->write(' '); + uint32_t v = fatType() == 32 ? getLe32(pc + 4 * k) : getLe16(pc + 2 * k); + printHex(pr, v); + } + pr->println(); + } +} +#endif // DOXYGEN_SHOULD_SKIP_THIS +#endif // ENABLE_ARDUINO_FEATURES diff --git a/third_party/sdfat/src/FatLib/FatFile.cpp b/third_party/sdfat/src/FatLib/FatFile.cpp new file mode 100644 index 00000000..c9f95a92 --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatFile.cpp @@ -0,0 +1,1505 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatFile.cpp" +#include "../common/DateLib.h" +#include "../common/DebugMacros.h" +#include "FatLib.h" +//------------------------------------------------------------------------------ +// Add a cluster to a file. +bool FatFile::addCluster() { +#if USE_FAT_FILE_FLAG_CONTIGUOUS + Cluster_t cc = m_curCluster; + if (!m_vol->allocateCluster(m_curCluster, &m_curCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + if (cc == 0) { + m_flags |= FILE_FLAG_CONTIGUOUS; + } else if (m_curCluster != (cc + 1)) { + m_flags &= ~FILE_FLAG_CONTIGUOUS; + } + m_flags |= FILE_FLAG_DIR_DIRTY; + return true; + +fail: + return false; +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + m_flags |= FILE_FLAG_DIR_DIRTY; + return m_vol->allocateCluster(m_curCluster, &m_curCluster); +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS +} +//------------------------------------------------------------------------------ +// Add a cluster to a directory file and zero the cluster. +// Return with first sector of cluster in the cache. +bool FatFile::addDirCluster() { + Sector_t sector; + uint8_t* pc; + + if (isRootFixed()) { + DBG_FAIL_MACRO; + goto fail; + } + // max folder size + if (m_curPosition >= 512UL * 4095) { + DBG_FAIL_MACRO; + goto fail; + } + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + sector = m_vol->clusterStartSector(m_curCluster); + for (uint8_t i = 0; i < m_vol->sectorsPerCluster(); i++) { + pc = m_vol->dataCachePrepare(sector + i, FsCache::CACHE_RESERVE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + memset(pc, 0, m_vol->bytesPerSector()); + } + // Set position to EOF to avoid inconsistent curCluster/curPosition. + m_curPosition += m_vol->bytesPerCluster(); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::attrib(uint8_t bits) { + if (!isFileOrSubDir() || (bits & FS_ATTRIB_USER_SETTABLE) != bits) { + DBG_FAIL_MACRO; + goto fail; + } + // Don't allow read-only to be set if the file is open for write. + if ((bits & FS_ATTRIB_READ_ONLY) && isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + m_attributes = (m_attributes & ~FS_ATTRIB_USER_SETTABLE) | bits; + // insure sync() will update dir entry + m_flags |= FILE_FLAG_DIR_DIRTY; + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// cache a file's directory entry +// return pointer to cached entry or null for failure +DirFat_t* FatFile::cacheDirEntry(uint8_t action) { + uint8_t* pc = m_vol->dataCachePrepare(m_dirSector, action); + DirFat_t* dir = reinterpret_cast(pc); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + return dir + (m_dirIndex & 0XF); + +fail: + return nullptr; +} +//------------------------------------------------------------------------------ +bool FatFile::close() { + bool rtn = sync(); + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return rtn; +} +//------------------------------------------------------------------------------ +bool FatFile::contiguousRange(Sector_t* bgnSector, Sector_t* endSector) { + // error if no clusters + if (!isFile() || m_firstCluster == 0) { + DBG_FAIL_MACRO; + goto fail; + } + for (Cluster_t c = m_firstCluster;; c++) { + Cluster_t next; + int8_t fg = m_vol->fatGet(c, &next); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + // check for contiguous + if (fg == 0 || next != (c + 1)) { + // error if not end of chain + if (fg) { + DBG_FAIL_MACRO; + goto fail; + } +#if USE_FAT_FILE_FLAG_CONTIGUOUS + m_flags |= FILE_FLAG_CONTIGUOUS; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + if (bgnSector) { + *bgnSector = m_vol->clusterStartSector(m_firstCluster); + } + if (endSector) { + *endSector = + m_vol->clusterStartSector(c) + m_vol->sectorsPerCluster() - 1; + } + return true; + } + } + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::createContiguous(const char* path, uint32_t size) { + if (!open(FatVolume::cwv(), path, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + if (preAllocate(size)) { + return true; + } + close(); +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::createContiguous(FatFile* dirFile, const char* path, + uint32_t size) { + if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + if (preAllocate(size)) { + return true; + } + close(); +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::dirEntry(DirFat_t* dst) { + const DirFat_t* dir; + // Make sure fields on device are correct. + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // read entry + dir = cacheDirEntry(FsCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy to caller's struct + memcpy(dst, dir, sizeof(DirFat_t)); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t FatFile::dirSize() { + int8_t fg; + if (!isDir()) { + return 0; + } + if (isRootFixed()) { + return FS_DIR_SIZE * m_vol->rootDirEntryCount(); + } + uint16_t n = 0; + Cluster_t c = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; + do { + fg = m_vol->fatGet(c, &c); + if (fg < 0 || n > 4095) { + return 0; + } + n += m_vol->sectorsPerCluster(); + } while (fg); + return 512UL * n; +} +//------------------------------------------------------------------------------ +int FatFile::fgets(char* str, int num, const char* delim) { + char ch; + int n = 0; + int r = -1; + while ((n + 1) < num && (r = read(&ch, 1)) == 1) { + // delete CR + if (ch == '\r') { + continue; + } + str[n++] = ch; + if (!delim) { + if (ch == '\n') { + break; + } + } else { + if (strchr(delim, ch)) { + break; + } + } + } + if (r < 0) { + // read error + return -1; + } + str[n] = '\0'; + return n; +} +//------------------------------------------------------------------------------ +void FatFile::fgetpos(fspos_t* pos) const { + pos->position = m_curPosition; + pos->cluster = m_curCluster; +} +//------------------------------------------------------------------------------ +Sector_t FatFile::firstSector() const { + return m_firstCluster ? m_vol->clusterStartSector(m_firstCluster) : 0; +} +//------------------------------------------------------------------------------ +void FatFile::fsetpos(const fspos_t* pos) { + m_curPosition = pos->position; + m_curCluster = pos->cluster; +} +//------------------------------------------------------------------------------ +bool FatFile::getAccessDate(uint16_t* pdate) { + DirFat_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(dir.accessDate); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { + DirFat_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(dir.createDate); + *ptime = getLe16(dir.createTime); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) { + DirFat_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(dir.modifyDate); + *ptime = getLe16(dir.modifyTime); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::isBusy() { return m_vol->isBusy(); } +//------------------------------------------------------------------------------ +bool FatFile::mkdir(FatFile* parent, const char* path, bool pFlag) { + FatName_t fname; + FatFile tmpDir; + + if (isOpen() || !parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (!tmpDir.openRoot(parent->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + parent = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!*path) { + break; + } + if (!open(parent, &fname, O_RDONLY)) { + if (!pFlag || !mkdir(parent, &fname)) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmpDir.copy(this); + parent = &tmpDir; + close(); + } + return mkdir(parent, &fname); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::mkdir(FatFile* parent, FatName_t* fname) { + Sector_t sector; + DirFat_t dot; + DirFat_t* dir; + uint8_t* pc; + + if (!parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + // create a normal file + if (!open(parent, fname, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + // convert file to directory + m_flags = FILE_FLAG_READ; + m_attributes = FILE_ATTR_SUBDIR; + + // allocate and zero first cluster + if (!addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + // Set to start of dir + rewind(); + // force entry to device + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // cache entry - should already be in cache due to sync() call + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // change directory entry attribute + dir->attributes = FS_ATTRIB_DIRECTORY; + + // make entry for '.' + memcpy(&dot, dir, sizeof(dot)); + dot.name[0] = '.'; + for (uint8_t i = 1; i < 11; i++) { + dot.name[i] = ' '; + } + + // cache sector for '.' and '..' + sector = m_vol->clusterStartSector(m_firstCluster); + pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE); + dir = reinterpret_cast(pc); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy '.' to sector + memcpy(&dir[0], &dot, sizeof(dot)); + // make entry for '..' + dot.name[1] = '.'; + setLe16(dot.firstClusterLow, parent->m_firstCluster & 0XFFFF); + setLe16(dot.firstClusterHigh, parent->m_firstCluster >> 16); + // copy '..' to sector + memcpy(&dir[1], &dot, sizeof(dot)); + // write first sector + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(const char* path, oflag_t oflag) { + return open(FatVolume::cwv(), path, oflag); +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatVolume* vol, const char* path, oflag_t oflag) { + return vol && open(vol->vwd(), path, oflag); +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, const char* path, oflag_t oflag) { + FatFile tmpDir; + FatName_t fname; + + // error if already open + if (isOpen() || !dirFile->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (*path == 0) { + return openRoot(dirFile->m_vol); + } + if (!tmpDir.openRoot(dirFile->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + dirFile = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (*path == 0) { + break; + } + if (!open(dirFile, &fname, O_RDONLY)) { + DBG_WARN_MACRO; + goto fail; + } + tmpDir.copy(this); + dirFile = &tmpDir; + close(); + } + return open(dirFile, &fname, oflag); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(uint16_t index, oflag_t oflag) { + FatVolume* vol = FatVolume::cwv(); + return vol ? open(vol->vwd(), index, oflag) : false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, uint16_t index, oflag_t oflag) { + if (index) { + // Find start of LFN. + const DirLfn_t* ldir; + uint8_t n = index < 20 ? index : 20; + for (uint8_t i = 1; i <= n; i++) { + ldir = reinterpret_cast(dirFile->cacheDir(index - i)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME) { + break; + } + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + if (!dirFile->seekSet(32UL * (index - i))) { + DBG_FAIL_MACRO; + goto fail; + } + break; + } + } + } else { + dirFile->rewind(); + } + if (!openNext(dirFile, oflag)) { + DBG_FAIL_MACRO; + goto fail; + } + if (dirIndex() != index) { + close(); + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// open a cached directory entry. +bool FatFile::openCachedEntry(FatFile* dirFile, uint16_t dirIndex, + oflag_t oflag, uint8_t lfnOrd) { + Cluster_t firstCluster; + memset(this, 0, sizeof(FatFile)); + // location of entry in cache + m_vol = dirFile->m_vol; + m_dirIndex = dirIndex; + m_dirCluster = dirFile->m_firstCluster; + DirFat_t* dir = reinterpret_cast(m_vol->cacheAddress()); + dir += 0XF & dirIndex; + + // Must be file or subdirectory. + if (!isFatFileOrSubdir(dir)) { + DBG_FAIL_MACRO; + goto fail; + } + m_attributes = dir->attributes & FS_ATTRIB_COPY; + if (isFatFile(dir)) { + m_attributes |= FILE_ATTR_FILE; + } + m_lfnOrd = lfnOrd; + + switch (oflag & O_ACCMODE) { + case O_RDONLY: + if (oflag & O_TRUNC) { + DBG_FAIL_MACRO; + goto fail; + } + m_flags = FILE_FLAG_READ; + break; + + case O_RDWR: + m_flags = FILE_FLAG_READ | FILE_FLAG_WRITE; + break; + + case O_WRONLY: + m_flags = FILE_FLAG_WRITE; + break; + + default: + DBG_FAIL_MACRO; + goto fail; + } + + if (m_flags & FILE_FLAG_WRITE) { + if (isSubDir() || isReadOnly()) { + DBG_FAIL_MACRO; + goto fail; + } + m_attributes |= FS_ATTRIB_ARCHIVE; + } + m_flags |= (oflag & O_APPEND) ? FILE_FLAG_APPEND : 0; + + m_dirSector = m_vol->cacheSectorNumber(); + + // copy first cluster number for directory fields + firstCluster = ((Cluster_t)getLe16(dir->firstClusterHigh) << 16) | + getLe16(dir->firstClusterLow); + + if (oflag & O_TRUNC) { + if (firstCluster && !m_vol->freeChain(firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // need to update directory entry + m_flags |= FILE_FLAG_DIR_DIRTY; + } else { + m_firstCluster = firstCluster; + m_fileSize = getLe32(dir->fileSize); + } + if ((oflag & O_AT_END) && !seekSet(m_fileSize)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openCluster(FatFile* file) { + if (file->m_dirCluster == 0) { + return openRoot(file->m_vol); + } + memset(this, 0, sizeof(FatFile)); + m_attributes = FILE_ATTR_SUBDIR; + m_flags = FILE_FLAG_READ; + m_vol = file->m_vol; + m_firstCluster = file->m_dirCluster; + return true; +} +//------------------------------------------------------------------------------ +bool FatFile::openCwd() { + if (isOpen() || !FatVolume::cwv()) { + DBG_FAIL_MACRO; + goto fail; + } + this->copy(FatVolume::cwv()->vwd()); + rewind(); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openNext(FatFile* dirFile, oflag_t oflag) { + uint8_t checksum = 0; + const DirLfn_t* ldir; + uint8_t lfnOrd = 0; + uint16_t index; + + // Check for not open and valid directory.. + if (isOpen() || !dirFile->isDir() || (dirFile->curPosition() & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + while (1) { + // read entry into cache + index = dirFile->curPosition() / FS_DIR_SIZE; + DirFat_t* dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + } + goto fail; + } + // done if last entry + if (dir->name[0] == FAT_NAME_FREE) { + goto fail; + } + // skip empty slot or '.' or '..' + if (dir->name[0] == '.' || dir->name[0] == FAT_NAME_DELETED) { + lfnOrd = 0; + } else if (isFatFileOrSubdir(dir)) { + if (lfnOrd && checksum != lfnChecksum(dir->name)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } else if (isFatLongName(dir)) { + ldir = reinterpret_cast(dir); + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + lfnOrd = ldir->order & 0X1F; + checksum = ldir->checksum; + } + } else { + lfnOrd = 0; + } + } + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openRoot(FatVolume* vol) { + // error if file is already open + if (isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + memset(this, 0, sizeof(FatFile)); + + m_vol = vol; + switch (vol->fatType()) { +#if FAT12_SUPPORT + case 12: +#endif // FAT12_SUPPORT + case 16: + m_attributes = FILE_ATTR_ROOT_FIXED; + break; + + case 32: + m_attributes = FILE_ATTR_ROOT32; + break; + + default: + DBG_FAIL_MACRO; + goto fail; + } + // read only + m_flags = FILE_FLAG_READ; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +int FatFile::peek() { + uint32_t saveCurPosition = m_curPosition; + Cluster_t saveCurCluster = m_curCluster; + int c = read(); + m_curPosition = saveCurPosition; + m_curCluster = saveCurCluster; + return c; +} +//------------------------------------------------------------------------------ +bool FatFile::preAllocate(uint32_t length) { + uint32_t need; + if (!length || !isWritable() || m_firstCluster) { + DBG_FAIL_MACRO; + goto fail; + } + need = 1 + ((length - 1) >> m_vol->bytesPerClusterShift()); + // allocate clusters + if (!m_vol->allocContiguous(need, &m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + m_fileSize = length; + +#if USE_FAT_FILE_FLAG_CONTIGUOUS + // Mark contiguous and insure sync() will update dir entry + m_flags |= FILE_FLAG_PREALLOCATE | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY; +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + // insure sync() will update dir entry + m_flags |= FILE_FLAG_DIR_DIRTY; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + return sync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +int FatFile::readPrivate(void* buf, size_t nbyte, DirFat_t** cache) { + int8_t fg; + uint8_t sectorOfCluster = 0; + uint8_t* dst = reinterpret_cast(buf); + uint16_t offset; + size_t toRead; + Sector_t sector; // raw device sector number + uint8_t* pc; + // error if not open for read + if (!isReadable()) { + DBG_FAIL_MACRO; + goto fail; + } + + if (isFile()) { + uint32_t tmp32 = m_fileSize - m_curPosition; + if (nbyte >= tmp32) { + nbyte = tmp32; + } + } else if (isRootFixed()) { + uint16_t tmp16 = FS_DIR_SIZE * m_vol->m_rootDirEntryCount - + static_cast(m_curPosition); + if (nbyte > tmp16) { + nbyte = tmp16; + } + } + toRead = nbyte; + while (toRead) { + size_t n; + offset = m_curPosition & m_vol->sectorMask(); // offset in sector + if (isRootFixed()) { + sector = m_vol->rootDirStart() + + (m_curPosition >> m_vol->bytesPerSectorShift()); + } else { + sectorOfCluster = m_vol->sectorOfCluster(m_curPosition); + if (offset == 0 && sectorOfCluster == 0) { + // start of new cluster + if (m_curPosition == 0) { + // use first cluster in file + m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; +#if USE_FAT_FILE_FLAG_CONTIGUOUS + } else if (isFile() && isContiguous()) { + m_curCluster++; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + } else { + // get next cluster from FAT + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg == 0) { + if (isDir()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + } + } + sector = m_vol->clusterStartSector(m_curCluster) + sectorOfCluster; + } + if (offset != 0 || toRead < m_vol->bytesPerSector() || + sector == m_vol->cacheSectorNumber()) { + // amount to be read from current sector + n = m_vol->bytesPerSector() - offset; + if (n > toRead) { + n = toRead; + } + // read sector to cache and copy data to caller + pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* src = pc + offset; + if (cache != nullptr) { + // Hook for readDirCache(). + *cache = reinterpret_cast(src); + } else { + memcpy(dst, src, n); + } +#if USE_MULTI_SECTOR_IO + } else if (toRead >= 2 * m_vol->bytesPerSector()) { + size_t ns = toRead >> m_vol->bytesPerSectorShift(); + if (!isRootFixed()) { + size_t mb = m_vol->sectorsPerCluster() - sectorOfCluster; + if (mb < ns) { + ns = mb; + } + } + n = ns << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeRead(sector, dst, ns)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + // read single sector + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeRead(sector, dst)) { + DBG_FAIL_MACRO; + goto fail; + } + } + dst += n; + m_curPosition += n; + toRead -= n; + } + return nbyte - toRead; + +fail: + m_error |= READ_ERROR; + return -1; +} +//------------------------------------------------------------------------------ +int8_t FatFile::readDir(DirFat_t* dir) { + // if not a directory file or miss-positioned return an error + if (!isDir() || (0X1F & m_curPosition)) { + return -1; + } + + while (1) { + int16_t n = read(dir, sizeof(DirFat_t)); + if (n != sizeof(DirFat_t)) { + return n == 0 ? 0 : -1; + } + // last entry if FAT_NAME_FREE + if (dir->name[0] == FAT_NAME_FREE) { + return 0; + } + // skip empty entries and entry for . and .. + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + continue; + } + // return if normal file or subdirectory + if (isFatFileOrSubdir(dir)) { + return n; + } + } +} +//------------------------------------------------------------------------------ +// Read next directory entry into the cache. +// Assumes file is correctly positioned. +DirFat_t* FatFile::readDirCache() { + DirFat_t* cache = nullptr; + DBG_HALT_IF(m_curPosition & 0X1F); + int n = readPrivate(nullptr, FS_DIR_SIZE, &cache); + if (n == FS_DIR_SIZE) { + return cache; + } + if (n != 0) { + DBG_FAIL_MACRO; + } + return nullptr; +} +//------------------------------------------------------------------------------ +bool FatFile::remove(const char* path) { + FatFile file; + if (!file.open(this, path, O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + return file.remove(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rename(const char* newPath) { + return rename(m_vol->vwd(), newPath); +} +//------------------------------------------------------------------------------ +bool FatFile::rename(FatFile* dirFile, const char* newPath) { + DirFat_t entry; + Cluster_t dirCluster = 0; + FatFile file; + FatFile oldFile; + uint8_t* pc; + DirFat_t* dir; + + // Must be an open file or subdirectory. + if (!(isFile() || isSubDir())) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't rename LFN in 8.3 mode. + if (!USE_LONG_FILE_NAMES && isLFN()) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't move file to new volume. + if (m_vol != dirFile->m_vol) { + DBG_FAIL_MACRO; + goto fail; + } + // sync() and cache directory entry + sync(); + oldFile.copy(this); + dir = cacheDirEntry(FsCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // save directory entry + memcpy(&entry, dir, sizeof(entry)); + // make directory entry for new path + if (isFile()) { + if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // don't create missing path prefix components + if (!file.mkdir(dirFile, newPath, false)) { + DBG_FAIL_MACRO; + goto fail; + } + // save cluster containing new dot dot + dirCluster = file.m_firstCluster; + } + // change to new directory entry + + m_dirSector = file.m_dirSector; + m_dirIndex = file.m_dirIndex; + m_lfnOrd = file.m_lfnOrd; + m_dirCluster = file.m_dirCluster; + // mark closed to avoid possible destructor close call + file.m_attributes = FILE_ATTR_CLOSED; + file.m_flags = 0; + + // cache new directory entry + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy all but name and name flags to new directory entry + memcpy(&dir->createTimeMs, &entry.createTimeMs, + sizeof(entry) - sizeof(dir->name) - 2); + dir->attributes = entry.attributes; + + // update dot dot if directory + if (dirCluster) { + // get new dot dot + Sector_t sector = m_vol->clusterStartSector(dirCluster); + pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + dir = reinterpret_cast(pc); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + memcpy(&entry, &dir[1], sizeof(entry)); + + // free unused cluster + if (!m_vol->freeChain(dirCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // store new dot dot + sector = m_vol->clusterStartSector(m_firstCluster); + pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE); + dir = reinterpret_cast(pc); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + memcpy(&dir[1], &entry, sizeof(entry)); + } + // Remove old directory entry; + oldFile.m_firstCluster = 0; + oldFile.m_flags = FILE_FLAG_WRITE; + oldFile.m_attributes = FILE_ATTR_FILE; + if (!oldFile.remove()) { + DBG_FAIL_MACRO; + goto fail; + } + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rmdir() { + // must be open subdirectory + if (!isSubDir() || (!USE_LONG_FILE_NAMES && isLFN())) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + + // make sure directory is empty + while (1) { + const DirFat_t* dir = readDirCache(); + if (!dir) { + // EOF if no error. + if (!getError()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + // done if past last used entry + if (dir->name[0] == FAT_NAME_FREE) { + break; + } + // skip empty slot, '.' or '..' + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + continue; + } + // error not empty + if (isFatFileOrSubdir(dir)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // convert empty directory to normal file for remove + m_attributes = FILE_ATTR_FILE; + m_flags |= FILE_FLAG_WRITE; + return remove(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rmRfStar() { + uint16_t index; + FatFile f; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (1) { + // remember position + index = m_curPosition / FS_DIR_SIZE; + + const DirFat_t* dir = readDirCache(); + if (!dir) { + // At EOF if no error. + if (!getError()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + // done if past last entry + if (dir->name[0] == FAT_NAME_FREE) { + break; + } + + // skip empty slot or '.' or '..' + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + continue; + } + + // skip if part of long file name or volume label in root + if (!isFatFileOrSubdir(dir)) { + continue; + } + + if (!f.open(this, index, O_RDONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + if (f.isSubDir()) { + // recursively delete + if (!f.rmRfStar()) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // ignore read-only + f.m_flags |= FILE_FLAG_WRITE; + if (!f.remove()) { + DBG_FAIL_MACRO; + goto fail; + } + } + // position to next entry if required + if (m_curPosition != (32UL * (index + 1))) { + if (!seekSet(32UL * (index + 1))) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + // don't try to delete root + if (!isRoot()) { + if (!rmdir()) { + DBG_FAIL_MACRO; + goto fail; + } + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::seekSet(uint32_t pos) { + uint32_t nCur; + uint32_t nNew; + Cluster_t tmp = m_curCluster; + // error if file not open + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Optimize O_APPEND writes. + if (pos == m_curPosition) { + return true; + } + if (pos == 0) { + // set position to start of file + m_curCluster = 0; + goto done; + } + if (isFile()) { + if (pos > m_fileSize) { + DBG_FAIL_MACRO; + goto fail; + } + } else if (isRootFixed()) { + if (pos <= FS_DIR_SIZE * m_vol->rootDirEntryCount()) { + goto done; + } + DBG_FAIL_MACRO; + goto fail; + } + // calculate cluster index for new position + nNew = (pos - 1) >> (m_vol->bytesPerClusterShift()); +#if USE_FAT_FILE_FLAG_CONTIGUOUS + if (isContiguous()) { + m_curCluster = m_firstCluster + nNew; + goto done; + } +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + // calculate cluster index for current position + nCur = (m_curPosition - 1) >> (m_vol->bytesPerClusterShift()); + + if (nNew < nCur || m_curPosition == 0) { + // must follow chain from first cluster + m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; + } else { + // advance from curPosition + nNew -= nCur; + } + while (nNew--) { + if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + +done: + m_curPosition = pos; + m_flags &= ~FILE_FLAG_PREALLOCATE; + return true; + +fail: + m_curCluster = tmp; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::sync() { + uint16_t date, time; + uint8_t ms10; + if (!isOpen()) { + return true; + } + if (m_flags & FILE_FLAG_DIR_DIRTY) { + DirFat_t* dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + // check for deleted by another open file object + if (!dir || dir->name[0] == FAT_NAME_DELETED) { + DBG_FAIL_MACRO; + goto fail; + } + dir->attributes = m_attributes & FS_ATTRIB_COPY; + // do not set filesize for dir files + if (isFile()) { + setLe32(dir->fileSize, m_fileSize); + } + // update first cluster fields + setLe16(dir->firstClusterLow, m_firstCluster & 0XFFFF); + setLe16(dir->firstClusterHigh, m_firstCluster >> 16); + + // set modify time if user supplied a callback date/time function + if (FsDateTime::callback) { + FsDateTime::callback(&date, &time, &ms10); + setLe16(dir->modifyDate, date); + setLe16(dir->accessDate, date); + setLe16(dir->modifyTime, time); + } + // clear directory dirty + m_flags &= ~FILE_FLAG_DIR_DIRTY; + } + if (m_vol->cacheSync()) { + return true; + } + DBG_FAIL_MACRO; + +fail: + m_error |= WRITE_ERROR; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, + uint8_t day, uint8_t hour, uint8_t minute, + uint8_t second) { + uint16_t dirDate; + uint16_t dirTime; + DirFat_t* dir; + + if (!isFileOrSubDir() || year < 1980 || year > 2099 || month < 1 || + month > 12 || day < 1 || day > daysInMonth(year, month) || hour > 23 || + minute > 59 || second > 59) { + DBG_FAIL_MACRO; + goto fail; + } + // update directory entry + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + dirDate = FS_DATE(year, month, day); + dirTime = FS_TIME(hour, minute, second); + if (flags & T_ACCESS) { + setLe16(dir->accessDate, dirDate); + } + if (flags & T_CREATE) { + setLe16(dir->createDate, dirDate); + setLe16(dir->createTime, dirTime); + // units of 10 ms + dir->createTimeMs = second & 1 ? 100 : 0; + } + if (flags & T_WRITE) { + setLe16(dir->modifyDate, dirDate); + setLe16(dir->modifyTime, dirTime); + } + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::truncate() { + uint32_t toFree; + // error if not a normal file or read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_firstCluster == 0) { + return true; + } + if (m_curCluster) { + toFree = 0; + int8_t fg = m_vol->fatGet(m_curCluster, &toFree); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg) { + // current cluster is end of chain + if (!m_vol->fatPutEOC(m_curCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + toFree = m_firstCluster; + m_firstCluster = 0; + } + if (toFree) { + if (!m_vol->freeChain(toFree)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_fileSize = m_curPosition; + + // need to update directory entry + m_flags |= FILE_FLAG_DIR_DIRTY; + return sync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::write(const void* buf, size_t nbyte) { + // convert void* to uint8_t* - must be before goto statements + const uint8_t* src = reinterpret_cast(buf); + uint8_t* pc; + uint8_t cacheOption; + // number of bytes left to write - must be before goto statements + size_t nToWrite = nbyte; + size_t n; + // error if not a normal file or is read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // seek to end of file if append flag + if ((m_flags & FILE_FLAG_APPEND)) { + if (!seekSet(m_fileSize)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Don't exceed max fileSize. + if (nbyte > (0XFFFFFFFF - m_curPosition)) { + DBG_FAIL_MACRO; + goto fail; + } + while (nToWrite) { + uint8_t sectorOfCluster = m_vol->sectorOfCluster(m_curPosition); + uint16_t sectorOffset = m_curPosition & m_vol->sectorMask(); + if (sectorOfCluster == 0 && sectorOffset == 0) { + // start of new cluster + if (m_curCluster != 0) { +#if USE_FAT_FILE_FLAG_CONTIGUOUS + int8_t fg; + if (isContiguous() && m_fileSize > m_curPosition) { + m_curCluster++; + fg = 1; + } else { + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + } +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + int8_t fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + if (fg == 0) { + // add cluster if at end of chain + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + if (m_firstCluster == 0) { + // allocate first cluster of file + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + } else { + m_curCluster = m_firstCluster; + } + } + } + // sector for data write + Sector_t sector = m_vol->clusterStartSector(m_curCluster) + sectorOfCluster; + + if (sectorOffset != 0 || nToWrite < m_vol->bytesPerSector()) { + // partial sector - must use cache + // max space in sector + n = m_vol->bytesPerSector() - sectorOffset; + // lesser of space and amount to write + if (n > nToWrite) { + n = nToWrite; + } + + if (sectorOffset == 0 && + (m_curPosition >= m_fileSize || m_flags & FILE_FLAG_PREALLOCATE)) { + // start of new sector don't need to read into cache + cacheOption = FsCache::CACHE_RESERVE_FOR_WRITE; + } else { + // rewrite part of sector + cacheOption = FsCache::CACHE_FOR_WRITE; + } + pc = m_vol->dataCachePrepare(sector, cacheOption); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* dst = pc + sectorOffset; + memcpy(dst, src, n); + if (m_vol->bytesPerSector() == (n + sectorOffset)) { + // Force write if sector is full - improves large writes. + if (!m_vol->cacheSyncData()) { + DBG_FAIL_MACRO; + goto fail; + } + } +#if USE_MULTI_SECTOR_IO + } else if (nToWrite >= 2 * m_vol->bytesPerSector()) { + // use multiple sector write command + size_t maxSectors = m_vol->sectorsPerCluster() - sectorOfCluster; + size_t nSector = nToWrite >> m_vol->bytesPerSectorShift(); + if (nSector > maxSectors) { + nSector = maxSectors; + } + n = nSector << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeWrite(sector, src, nSector)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + // use single sector write command + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeWrite(sector, src)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_curPosition += n; + src += n; + nToWrite -= n; + } + if (m_curPosition > m_fileSize) { + // update fileSize and insure sync will update dir entry + m_fileSize = m_curPosition; + m_flags |= FILE_FLAG_DIR_DIRTY; + } else if (FsDateTime::callback) { + // insure sync will update modified date and time + m_flags |= FILE_FLAG_DIR_DIRTY; + } + return nbyte; + +fail: + // return for write error + m_error |= WRITE_ERROR; + return 0; +} diff --git a/third_party/sdfat/src/FatLib/FatFile.h b/third_party/sdfat/src/FatLib/FatFile.h new file mode 100644 index 00000000..df9df530 --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatFile.h @@ -0,0 +1,1107 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief FatFile class + */ +#include +#include +#include + +#include "../common/FmtNumber.h" +#include "../common/FsApiConstants.h" +#include "../common/FsDateTime.h" +#include "../common/FsName.h" +#include "FatPartition.h" +class FatVolume; +//------------------------------------------------------------------------------ +/** + * \struct FatPos_t + * \brief Internal type for file position - do not use in user apps. + */ +struct FatPos_t { + /** stream position */ + uint32_t position; + /** cluster for position */ + Cluster_t cluster; +}; +//------------------------------------------------------------------------------ +/** Expression for path name separator. */ +#define isDirSeparator(c) ((c) == '/') +//------------------------------------------------------------------------------ +/** + * \class FatLfn_t + * \brief Internal type for Long File Name - do not use in user apps. + */ + +class FatLfn_t : public FsName { + public: + /** UTF-16 length of Long File Name */ + size_t len; + /** Position for sequence number. */ + uint8_t seqPos; + /** Flags for base and extension character case and LFN. */ + uint8_t flags; + /** Short File Name */ + uint8_t sfn[11]; +}; +/** + * \class FatSfn_t + * \brief Internal type for Short 8.3 File Name - do not use in user apps. + */ +class FatSfn_t { + public: + /** Flags for base and extension character case and LFN. */ + uint8_t flags; + /** Short File Name */ + uint8_t sfn[11]; +}; + +#if USE_LONG_FILE_NAMES +/** Internal class for file names */ +typedef FatLfn_t FatName_t; +#else // USE_LONG_FILE_NAMES +/** Internal class for file names */ +typedef FatSfn_t FatName_t; +#endif // USE_LONG_FILE_NAMES + +/** Derived from a LFN with loss or conversion of characters. */ +const uint8_t FNAME_FLAG_LOST_CHARS = 0X01; +/** Base-name or extension has mixed case. */ +const uint8_t FNAME_FLAG_MIXED_CASE = 0X02; +/** LFN entries are required for file name. */ +const uint8_t FNAME_FLAG_NEED_LFN = + FNAME_FLAG_LOST_CHARS | FNAME_FLAG_MIXED_CASE; +/** Filename base-name is all lower case */ +const uint8_t FNAME_FLAG_LC_BASE = FAT_CASE_LC_BASE; +/** Filename extension is all lower case. */ +const uint8_t FNAME_FLAG_LC_EXT = FAT_CASE_LC_EXT; +//============================================================================== +/** + * \class FatFile + * \brief Basic file class. + */ +class FatFile { + public: + /** Create an instance. */ + FatFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). + */ + FatFile(const char* path, oflag_t oflag) { open(path, oflag); } + + /** Copy from to this. + * \param[in] from Source file. + */ + void copy(const FatFile* from) { + if (from != this) { +#if FILE_COPY_CONSTRUCTOR_SELECT + *this = *from; +#else // FILE_COPY_CONSTRUCTOR_SELECT + memcpy(this, from, sizeof(FatFile)); +#endif // FILE_COPY_CONSTRUCTOR_SELECT + } + } + /** move from to this. + * \param[in] from Source file. + */ + void move(FatFile* from) { + if (from != this) { + copy(from); + from->m_attributes = FILE_ATTR_CLOSED; + } + } + +#if FILE_COPY_CONSTRUCTOR_SELECT == FILE_COPY_CONSTRUCTOR_PUBLIC + /** Copy constructor. + * \param[in] from Move from file. + * + */ + FatFile(const FatFile& from) = default; + /** Copy assignment operator. + * \param[in] from Move from file. + * \return Copied file. + */ + FatFile& operator=(const FatFile& from) = default; +#elif FILE_COPY_CONSTRUCTOR_SELECT == FILE_COPY_CONSTRUCTOR_PRIVATE + + private: + FatFile(const FatFile& from) = default; + FatFile& operator=(const FatFile& from) = default; + + public: +#else // FILE_COPY_CONSTRUCTOR_SELECT + FatFile(const FatFile& from) = delete; + FatFile& operator=(const FatFile& from) = delete; +#endif // FILE_COPY_CONSTRUCTOR_SELECT + +#if FILE_MOVE_CONSTRUCTOR_SELECT + /** Move constructor. + * \param[in] from Move from file. + */ + FatFile(FatFile&& from) { move(&from); } + /** Move assignment operator. + * \param[in] from Move from file. + * \return Moved file. + */ + FatFile& operator=(FatFile&& from) { + move(&from); + return *this; + } +#else // FILE_MOVE_CONSTRUCTOR_SELECT + FatFile(FatFile&& from) = delete; + FatFile& operator=(FatFile&& from) = delete; +#endif + /** Destructor */ +#if DESTRUCTOR_CLOSES_FILE + ~FatFile() { + if (isOpen()) { + close(); + } + } +#else // DESTRUCTOR_CLOSES_FILE + ~FatFile() = default; +#endif // DESTRUCTOR_CLOSES_FILE + + /** The parenthesis operator. + * + * \return true if a file is open. + */ + operator bool() const { return isOpen(); } + /** + * \return user settable file attributes for success else -1. + */ + int attrib() { + return isFileOrSubDir() ? m_attributes & FS_ATTRIB_USER_SETTABLE : -1; + } + /** Set file attributes + * + * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY, + * FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE. + * + * \note attrib() will fail for set read-only if the file is open for write. + * \return true for success or false for failure. + */ + bool attrib(uint8_t bits); + /** \return The number of bytes available from the current position + * to EOF for normal files. INT_MAX is returned for very large files. + * + * available32() is recomended for very large files. + * + * Zero is returned for directory files. + * + */ + int available() const { + uint32_t n = available32(); + return n > INT_MAX ? INT_MAX : n; + } + /** \return The number of bytes available from the current position + * to EOF for normal files. Zero is returned for directory files. + */ + uint32_t available32() const { + return isFile() ? fileSize() - curPosition() : 0; + } + /** Clear all error bits. */ + void clearError() { m_error = 0; } + /** Set writeError to zero */ + void clearWriteError() { m_error &= ~WRITE_ERROR; } + /** Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool close(); + /** Check for contiguous file and return its raw sector range. + * + * \param[out] bgnSector the first sector address for the file. + * \param[out] endSector the last sector address for the file. + * + * Set the contiguous flag if the file is contiguous. + * The parameters may be nullptr to only set the flag. + * \return true for success or false for failure. + */ + bool contiguousRange(Sector_t* bgnSector, Sector_t* endSector); + /** Create and open a new contiguous file of a specified size. + * + * \param[in] dirFile The directory where the file will be created. + * \param[in] path A path with a valid file name. + * \param[in] size The desired file size. + * + * \return true for success or false for failure. + */ + bool createContiguous(FatFile* dirFile, const char* path, uint32_t size); + /** Create and open a new contiguous file of a specified size. + * + * \param[in] path A path with a valid file name. + * \param[in] size The desired file size. + * + * \return true for success or false for failure. + */ + bool createContiguous(const char* path, uint32_t size); + /** \return The current cluster number for a file or directory. */ + Cluster_t curCluster() const { return m_curCluster; } + + /** \return The current position for a file or directory. */ + uint32_t curPosition() const { return m_curPosition; } + /** Return a file's directory entry. + * + * \param[out] dir Location for return of the file's directory entry. + * + * \return true for success or false for failure. + */ + bool dirEntry(DirFat_t* dir); + /** \return Directory entry index. */ + uint16_t dirIndex() const { return m_dirIndex; } + /** \return The number of bytes allocated to a directory or zero + * if an error occurs. + */ + uint32_t dirSize(); + /** Dump file in Hex + * \param[in] pr Print stream for list. + * \param[in] pos Start position in file. + * \param[in] n number of locations to dump. + */ + void dmpFile(print_t* pr, uint32_t pos, size_t n); + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * The calling instance must be an open directory file. + * + * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory + * dirFile. + * + * \return True if the file exists. + */ + bool exists(const char* path) { + FatFile file; + return file.open(this, path, O_RDONLY); + } + /** get position for streams + * \param[out] pos struct to receive position + */ + void fgetpos(fspos_t* pos) const; + /** + * Get a string from a file. + * + * fgets() reads bytes from a file into the array pointed to by \a str, until + * \a num - 1 bytes are read, or a delimiter is read and transferred to + * \a str, or end-of-file is encountered. The string is then terminated + * with a null byte. + * + * fgets() deletes CR, '\\r', from the string. This insures only a '\\n' + * terminates the string for Windows text files which use CRLF for newline. + * + * \param[out] str Pointer to the array where the string is stored. + * \param[in] num Maximum number of characters to be read + * (including the final null byte). Usually the length + * of the array \a str is used. + * \param[in] delim Optional set of delimiters. The default is "\n". + * + * \return For success fgets() returns the length of the string in \a str. + * If no data is read, fgets() returns zero for EOF or -1 if an error + * occurred. + */ + int fgets(char* str, int num, const char* delim = nullptr); + /** \return The first cluster number for a file or directory. */ + Cluster_t firstCluster() const { return m_firstCluster; } + /** \return The total number of bytes in a file. */ + uint32_t fileSize() const { return m_fileSize; } + /** \return first sector of file or zero for empty file. */ + Sector_t firstBlock() const { return firstSector(); } + /** \return Address of first sector or zero for empty file. */ + Sector_t firstSector() const; + /** Arduino name for sync() */ + void flush() { sync(); } + /** set position for streams + * \param[in] pos struct with value for new position + */ + void fsetpos(const fspos_t* pos); + /** Get a file's access date. + * + * \param[out] pdate Packed date for directory entry. + * + * \return true for success or false for failure. + */ + bool getAccessDate(uint16_t* pdate); + /** Get a file's access date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime return zero since FAT has no time. + * + * This function is for comparability in FsFile. + * + * \return true for success or false for failure. + */ + bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { + if (!getAccessDate(pdate)) { + return false; + } + *ptime = 0; + return true; + } + /** Get a file's create date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime); + /** \return All error bits. */ + uint8_t getError() const { return m_error; } + /** Get a file's modify date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime); + /** + * Get a file's name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in bytes. The array + * must be at least 13 bytes long. + * \return length for success or zero for failure. + */ + size_t getName(char* name, size_t size); + /** + * Get a file's ASCII name followed by a zero. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in characters. + * \return length for success or zero for failure. + */ + size_t getName7(char* name, size_t size); + /** + * Get a file's UTF-8 name followed by a zero. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in characters. + * \return length for success or zero for failure. + */ + size_t getName8(char* name, size_t size); +#ifndef DOXYGEN_SHOULD_SKIP_THIS + size_t __attribute__((error("use getSFN(name, size)"))) getSFN(char* name); +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** + * Get a file's Short File Name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * The array should be at least 13 bytes long. + * \param[in] size size of name array. + * \return true for success or false for failure. + */ + size_t getSFN(char* name, size_t size); + /** \return value of writeError */ + bool getWriteError() const { return isOpen() ? m_error & WRITE_ERROR : true; } + /** + * Check for device busy. + * + * \return true if busy else false. + */ + bool isBusy(); +#if USE_FAT_FILE_FLAG_CONTIGUOUS + /** \return True if the file is contiguous. */ + bool isContiguous() const { return m_flags & FILE_FLAG_CONTIGUOUS; } +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + /** \return True if this is a directory. */ + bool isDir() const { return m_attributes & FILE_ATTR_DIR; } + /** \return True if this is a normal file. */ + bool isFile() const { return m_attributes & FILE_ATTR_FILE; } + /** \return True if this is a normal file or sub-directory. */ + bool isFileOrSubDir() const { return isFile() || isSubDir(); } + /** \return True if this is a hidden file. */ + bool isHidden() const { return m_attributes & FS_ATTRIB_HIDDEN; } + /** \return true if this file has a Long File Name. */ + bool isLFN() const { return m_lfnOrd; } + /** \return True if this is an open file/directory. */ + bool isOpen() const { return m_attributes; } + /** \return True file is readable. */ + bool isReadable() const { return m_flags & FILE_FLAG_READ; } + /** \return True if file is read-only */ + bool isReadOnly() const { return m_attributes & FS_ATTRIB_READ_ONLY; } + /** \return True if this is the root directory. */ + bool isRoot() const { return m_attributes & FILE_ATTR_ROOT; } + /** \return True if this is the FAT32 root directory. */ + bool isRoot32() const { return m_attributes & FILE_ATTR_ROOT32; } + /** \return True if this is the FAT12 of FAT16 root directory. */ + bool isRootFixed() const { return m_attributes & FILE_ATTR_ROOT_FIXED; } + /** \return True if this is a sub-directory. */ + bool isSubDir() const { return m_attributes & FILE_ATTR_SUBDIR; } + /** \return True if this is a system file. */ + bool isSystem() const { return m_attributes & FS_ATTRIB_SYSTEM; } + /** \return True file is writable. */ + bool isWritable() const { return m_flags & FILE_FLAG_WRITE; } + /** List directory contents. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \param[in] indent Amount of space before file name. Used for recursive + * list to indicate subdirectory level. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags = 0, uint8_t indent = 0); + /** Make a new directory. + * + * \param[in] dir An open FatFile instance for the directory that will + * contain the new directory. + * + * \param[in] path A path with a valid name for the new directory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(FatFile* dir, const char* path, bool pFlag = true); + /** Open a file in the volume root directory. + * + * \param[in] vol Volume where the file is located. + * + * \param[in] path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(FatVolume* vol, const char* path, oflag_t oflag = O_RDONLY); + /** Open a file by index. + * + * \param[in] dirFile An open FatFile instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(FatFile* dirFile, uint16_t index, oflag_t oflag = O_RDONLY); + /** Open a file by index in the current working directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(uint16_t index, oflag_t oflag = O_RDONLY); + /** Open a file or directory by name. + * + * \param[in] dirFile An open FatFile instance for the directory containing + * the file to be opened. + * + * \param[in] path A path with a valid name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of flags from the following list. + * Only one of O_RDONLY, O_READ, O_WRONLY, O_WRITE, or + * O_RDWR is allowed. + * + * O_RDONLY - Open for reading. + * + * O_READ - Same as O_RDONLY. + * + * O_WRONLY - Open for writing. + * + * O_WRITE - Same as O_WRONLY. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_AT_END - Set the initial position at the end of the file. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file + * exists. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated + * to 0. + * + * WARNING: A given file must not be opened by more than one FatFile object + * or file corruption may occur. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return true for success or false for failure. + */ + bool open(FatFile* dirFile, const char* path, oflag_t oflag = O_RDONLY); + /** Open a file in the current working volume. + * + * \param[in] path A path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(const char* path, oflag_t oflag = O_RDONLY); + /** Open the current working directory. + * + * \return true for success or false for failure. + */ + bool openCwd(); + /** Open existing file wih Short 8.3 names. + * \param[in] path with short 8.3 names. + * + * the purpose of this function is to save flash on Uno + * and other small boards. + * + * Directories will be opened O_RDONLY, files O_RDWR. + * \return true for success or false for failure. + */ + bool openExistingSFN(const char* path); + /** Open the next file or subdirectory in a directory. + * + * \param[in] dirFile An open FatFile instance for the directory + * containing the file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool openNext(FatFile* dirFile, oflag_t oflag = O_RDONLY); + /** Open a volume's root directory. + * + * \param[in] vol The FAT volume containing the root directory to be opened. + * + * \return true for success or false for failure. + */ + bool openRoot(FatVolume* vol); + + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek(); + /** Allocate contiguous clusters to an empty file. + * + * The file must be empty with no clusters allocated. + * + * The file will contain uninitialized data. + * + * \param[in] length size of the file in bytes. + * \return true for success or false for failure. + */ + bool preAllocate(uint32_t length); + /** Print a file's access date + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed. + */ + size_t printAccessDate(print_t* pr); + /** Print a file's access date + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed. + */ + size_t printAccessDateTime(print_t* pr) { return printAccessDate(pr); } + /** Print a file's creation date and time + * + * \param[in] pr Print stream for output. + * + * \return The number of bytes printed. + */ + size_t printCreateDateTime(print_t* pr); + /** %Print a directory date field. + * + * Format is yyyy-mm-dd. + * + * \param[in] pr Print stream for output. + * \param[in] fatDate The date field from a directory entry. + */ + static void printFatDate(print_t* pr, uint16_t fatDate); + /** %Print a directory time field. + * + * Format is hh:mm:ss. + * + * \param[in] pr Print stream for output. + * \param[in] fatTime The time field from a directory entry. + */ + static void printFatTime(print_t* pr, uint16_t fatTime); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, value, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + char sign = 0; + char buf[3 * sizeof(Type) + 3]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + if (value < 0) { + value = -value; + sign = '-'; + } + if (sizeof(Type) < 4) { + str = fmtBase10(str, static_cast(value)); + } else { + str = fmtBase10(str, static_cast(value)); + } + if (sign) { + *--str = sign; + } + return write(str, &buf[sizeof(buf)] - str); + } + /** Print a file's size. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printFileSize(print_t* pr); + /** Print a file's modify date and time + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed. + */ + size_t printModifyDateTime(print_t* pr); + /** Print a file's name + * + * \param[in] pr Print stream for output. + * + * \return length for success or zero for failure. + */ + size_t printName(print_t* pr); + /** Print a file's ASCII name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName7(print_t* pr); + /** Print a file's UTF-8 name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName8(print_t* pr); + /** Print a file's Short File Name. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printSFN(print_t* pr); + /** Read the next byte from a file. + * + * \return For success read returns the next byte in the file as an int. + * If an error occurs or end of file is reached -1 is returned. + */ + int read() { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + /** Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] count Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a nbyte, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. + */ + int read(void* buf, size_t count) { return readPrivate(buf, count, nullptr); } + /** Read the next directory entry from a directory file. + * + * \param[out] dir The DirFat_t struct that will receive the data. + * + * \return For success readDir() returns the number of bytes read. + * A value of zero will be returned if end of file is reached. + * If an error occurs, readDir() returns -1. Possible errors include + * readDir() called before a directory has been opened, this is not + * a directory file or an I/O error occurred. + */ + int8_t readDir(DirFat_t* dir); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] path Path for the file to be removed. + * + * Example use: dirFile.remove(filenameToRemove); + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(const char* path); + /** Rename a file or subdirectory. + * \note the renamed file will be moved to the current volume working + * directory. + * + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(const char* newPath); + /** Rename a file or subdirectory. + * + * \param[in] dirFile Directory for the new path. + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(FatFile* dirFile, const char* newPath); + /** Set the file's current position to zero. */ + void rewind() { seekSet(0UL); } + /** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmdir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return true for success or false for failure. + */ + bool rmdir(); + /** Recursively delete a directory and all contained files. + * + * This is like the Unix/Linux 'rm -rf *' if called with the root directory + * hence the name. + * + * Warning - This will remove all contents of the directory including + * subdirectories. The directory will then be removed if it is not root. + * The read-only attribute for files will be ignored. + * + * \note This function should not be used to delete the 8.3 version of + * a directory that has a long name. See remove() and rmdir(). + * + * \return true for success or false for failure. + */ + bool rmRfStar(); + /** Set the files position to current position + \a pos. See seekSet(). + * \param[in] offset The new position in bytes from the current position. + * \return true for success or false for failure. + */ + bool seekCur(int32_t offset) { return seekSet(m_curPosition + offset); } + /** Set the files position to end-of-file + \a offset. See seekSet(). + * Can't be used for directory files since file size is not defined. + * \param[in] offset The new position in bytes from end-of-file. + * \return true for success or false for failure. + */ + bool seekEnd(int32_t offset = 0) { + return isFile() ? seekSet(m_fileSize + offset) : false; + } + /** Sets a file's position. + * + * \param[in] pos The new position in bytes from the beginning of the file. + * + * \return true for success or false for failure. + */ + bool seekSet(uint32_t pos); + /** The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool sync(); + /** Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive + * OR of flags from the following list + * + * T_ACCESS - Set the file's last access date. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2099 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return true for success or false for failure. + */ + bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second); + + /** Truncate a file at the current file position. + * will be maintained if it is less than or equal to \a length otherwise + * it will be set to end of file. + * + * \return true for success or false for failure. + */ + bool truncate(); + /** Truncate a file to a specified length. The current file position + * will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(uint32_t length) { return seekSet(length) && truncate(); } + /** Write a string to a file. Used by the Arduino Print class. + * \param[in] str Pointer to the string. + * Use getWriteError to check for errors. + * \return count of characters written for success or -1 for failure. + */ + size_t write(const char* str) { return write(str, strlen(str)); } + /** Write a single byte. + * \param[in] b The byte to be written. + * \return +1 for success or -1 for failure. + */ + size_t write(uint8_t b) { return write(&b, 1); } + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] count Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a count. If an error occurs, write() returns zero and writeError is set. + * + */ + size_t write(const void* buf, size_t count); +//------------------------------------------------------------------------------ +#if ENABLE_ARDUINO_SERIAL + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags = 0) { return ls(&Serial, flags); } + /** Print a file's name. + * + * \return length for success or zero for failure. + */ + size_t printName() { return FatFile::printName(&Serial); } +#endif // ENABLE_ARDUINO_SERIAL + + private: + /** FatVolume allowed access to private members. */ + friend class FatVolume; + + /** This file has not been opened. */ + static const uint8_t FILE_ATTR_CLOSED = 0; + /** Entry for normal data file */ + static const uint8_t FILE_ATTR_FILE = 0X08; + /** Entry is for a subdirectory */ + static const uint8_t FILE_ATTR_SUBDIR = FS_ATTRIB_DIRECTORY; + /** A FAT12 or FAT16 root directory */ + static const uint8_t FILE_ATTR_ROOT_FIXED = 0X40; + /** A FAT32 root directory */ + static const uint8_t FILE_ATTR_ROOT32 = 0X80; + /** Entry is for root. */ + static const uint8_t FILE_ATTR_ROOT = FILE_ATTR_ROOT_FIXED | FILE_ATTR_ROOT32; + /** Directory type bits */ + static const uint8_t FILE_ATTR_DIR = FILE_ATTR_SUBDIR | FILE_ATTR_ROOT; + + // private functions + + bool addCluster(); + bool addDirCluster(); + DirFat_t* cacheDir(uint16_t index) { + return seekSet(32UL * index) ? readDirCache() : nullptr; + } + DirFat_t* cacheDirEntry(uint8_t action); + bool cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd); + bool createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd); + uint16_t getLfnChar(const DirLfn_t* ldir, uint8_t i); + uint8_t lfnChecksum(const uint8_t* name) { + uint8_t sum = 0; + for (uint8_t i = 0; i < 11; i++) { + sum = (((sum & 1) << 7) | (sum >> 1)) + name[i]; + } + return sum; + } + static bool makeSFN(FatLfn_t* fname); + bool makeUniqueSfn(FatLfn_t* fname); + bool openCluster(FatFile* file); + bool parsePathName(const char* str, FatLfn_t* fname, const char** ptr); + bool parsePathName(const char* str, FatSfn_t* fname, const char** ptr); + bool mkdir(FatFile* parent, FatName_t* fname); + bool open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag); + bool open(FatFile* dirFile, const FatSfn_t* fname, oflag_t oflag); + bool openSFN(const FatSfn_t* fname); + bool openCachedEntry(FatFile* dirFile, uint16_t cacheIndex, oflag_t oflag, + uint8_t lfnOrd); + DirFat_t* readDirCache(); + int readPrivate(void* buf, size_t nbyte, DirFat_t** cache); + // bits defined in m_flags + static const uint8_t FILE_FLAG_READ = 0X01; + static const uint8_t FILE_FLAG_WRITE = 0X02; + static const uint8_t FILE_FLAG_APPEND = 0X08; + // treat curPosition as valid length. + static const uint8_t FILE_FLAG_PREALLOCATE = 0X20; + // file is contiguous + static const uint8_t FILE_FLAG_CONTIGUOUS = 0X40; + // sync of directory entry required + static const uint8_t FILE_FLAG_DIR_DIRTY = 0X80; + + // private data + static const uint8_t WRITE_ERROR = 0X1; + static const uint8_t READ_ERROR = 0X2; + + uint8_t m_attributes = FILE_ATTR_CLOSED; + uint8_t m_error = 0; // Error bits. + uint8_t m_flags = 0; // See above for definition of m_flags bits + uint8_t m_lfnOrd; + uint16_t m_dirIndex; // index of directory entry in dir file + FatVolume* m_vol; // volume where file is located + Cluster_t m_dirCluster; + Cluster_t m_curCluster; // cluster for current file position + uint32_t m_curPosition; // current file position + Sector_t m_dirSector; // sector for this files directory entry + uint32_t m_fileSize; // file size in bytes + Cluster_t m_firstCluster; // first cluster of file +}; + +#include "../common/ArduinoFiles.h" +/** + * \class File32 + * \brief FAT16/FAT32 file with Arduino Stream. + */ +class File32 : public StreamFile { + public: + File32() {} + /** Create an open File32. + * \param[in] path path for file. + * \param[in] oflag open flags. + */ + File32(const char* path, oflag_t oflag) { open(path, oflag); } + /** Opens the next file or folder in a directory. + * + * \param[in] oflag open flags. + * \return a FatStream object. + */ + File32 openNextFile(oflag_t oflag = O_RDONLY) { + File32 tmpFile; + tmpFile.openNext(this, oflag); + return tmpFile; + } +}; diff --git a/third_party/sdfat/src/FatLib/FatFileLFN.cpp b/third_party/sdfat/src/FatLib/FatFileLFN.cpp new file mode 100644 index 00000000..9864acdc --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatFileLFN.cpp @@ -0,0 +1,581 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatFileLFN.cpp" +#include "../common/DebugMacros.h" +#include "../common/FsUtf.h" +#include "../common/upcase.h" +#include "FatLib.h" +#if USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +static bool isLower(char c) { return 'a' <= c && c <= 'z'; } +//------------------------------------------------------------------------------ +static bool isUpper(char c) { return 'A' <= c && c <= 'Z'; } +//------------------------------------------------------------------------------ +// A bit smaller than toupper in AVR 328. +inline char toUpper(char c) { return isLower(c) ? c - 'a' + 'A' : c; } +//------------------------------------------------------------------------------ +/** + * Store a 16-bit long file name character. + * + * \param[in] ldir Pointer to long file name directory entry. + * \param[in] i Index of character. + * \param[in] c The 16-bit character. + */ +static void putLfnChar(DirLfn_t* ldir, uint8_t i, uint16_t c) { + if (i < 5) { + setLe16(ldir->unicode1 + 2 * i, c); + } else if (i < 11) { + setLe16(ldir->unicode2 + 2 * (i - 5), c); + } else if (i < 13) { + setLe16(ldir->unicode3 + 2 * (i - 11), c); + } +} +//============================================================================== +bool FatFile::cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { + FatFile dir; + dir.copy(this); + const DirLfn_t* ldir; + fname->reset(); + for (uint8_t order = 1; order <= lfnOrd; order++) { + ldir = reinterpret_cast(dir.cacheDir(index - order)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + // These should be checked in caller. + DBG_HALT_IF(ldir->attributes != FAT_ATTRIB_LONG_NAME); + DBG_HALT_IF(order != (ldir->order & 0X1F)); + for (uint8_t i = 0; i < 13; i++) { + uint16_t u = getLfnChar(ldir, i); + if (fname->atEnd()) { + return u == 0; + } +#if USE_UTF8_LONG_NAMES + uint16_t cp = fname->get16(); + // Make sure caller checked for valid UTF-8. + DBG_HALT_IF(cp == 0XFFFF); + if (toUpcase(u) != toUpcase(cp)) { + return false; + } +#else // USE_UTF8_LONG_NAMES + if (u > 0X7F || toUpper(u) != toUpper(fname->getch())) { + return false; + } +#endif // USE_UTF8_LONG_NAMES + } + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { + FatFile dir; + dir.copy(this); + DirLfn_t* ldir; + uint8_t checksum = lfnChecksum(fname->sfn); + uint8_t fc = 0; + fname->reset(); + + for (uint8_t order = 1; order <= lfnOrd; order++) { + ldir = reinterpret_cast(dir.cacheDir(index - order)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + dir.m_vol->cacheDirty(); + ldir->order = order == lfnOrd ? FAT_ORDER_LAST_LONG_ENTRY | order : order; + ldir->attributes = FAT_ATTRIB_LONG_NAME; + ldir->mustBeZero1 = 0; + ldir->checksum = checksum; + setLe16(ldir->mustBeZero2, 0); + for (uint8_t i = 0; i < 13; i++) { + uint16_t cp; + if (fname->atEnd()) { + cp = fc++ ? 0XFFFF : 0; + } else { + cp = fname->get16(); + // Verify caller checked for valid UTF-8. + DBG_HALT_IF(cp == 0XFFFF); + } + putLfnChar(ldir, i, cp); + } + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::makeSFN(FatLfn_t* fname) { + bool is83; + // char c; + uint8_t c; + uint8_t bit = FAT_CASE_LC_BASE; + uint8_t lc = 0; + uint8_t uc = 0; + uint8_t i = 0; + uint8_t in = 7; + const char* dot; + const char* end = fname->end; + const char* ptr = fname->begin; + + // Assume not zero length. + DBG_HALT_IF(end == ptr); + // Assume blanks removed from start and end. + DBG_HALT_IF(*ptr == ' ' || *(end - 1) == ' ' || *(end - 1) == '.'); + + // Blank file short name. + for (uint8_t k = 0; k < 11; k++) { + fname->sfn[k] = ' '; + } + // Not 8.3 if starts with dot. + is83 = *ptr == '.' ? false : true; + // Skip leading dots. + for (; *ptr == '.'; ptr++) { + } + // Find last dot. + for (dot = end - 1; dot > ptr && *dot != '.'; dot--) { + } + + for (; ptr < end; ptr++) { + c = *ptr; + if (c == '.' && ptr == dot) { + in = 10; // Max index for full 8.3 name. + i = 8; // Place for extension. + bit = FAT_CASE_LC_EXT; // bit for extension. + } else { + if (sfnReservedChar(c)) { + is83 = false; + // Skip UTF-8 trailing characters. + if ((c & 0XC0) == 0X80) { + continue; + } + c = '_'; + } + if (i > in) { + is83 = false; + if (in == 10 || ptr > dot) { + // Done - extension longer than three characters or no extension. + break; + } + // Skip to dot. + ptr = dot - 1; + continue; + } + if (isLower(c)) { + c += 'A' - 'a'; + lc |= bit; + } else if (isUpper(c)) { + uc |= bit; + } + fname->sfn[i++] = c; + if (i < 7) { + fname->seqPos = i; + } + } + } + if (fname->sfn[0] == ' ') { + DBG_HALT_MACRO; + goto fail; + } + if (is83) { + fname->flags = (lc & uc) ? FNAME_FLAG_MIXED_CASE : lc; + } else { + fname->flags = FNAME_FLAG_LOST_CHARS; + fname->sfn[fname->seqPos] = '~'; + fname->sfn[fname->seqPos + 1] = '1'; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::makeUniqueSfn(FatLfn_t* fname) { + const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 + uint8_t pos = fname->seqPos; + const DirFat_t* dir; + uint16_t hex = 0; + + DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); + DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1'); + + for (uint8_t seq = FIRST_HASH_SEQ; seq < 100; seq++) { + DBG_WARN_IF(seq > FIRST_HASH_SEQ); + hex += millis(); + if (pos > 3) { + // Make space in name for ~HHHH. + pos = 3; + } + for (uint8_t i = pos + 4; i > pos; i--) { + uint8_t h = hex & 0XF; + fname->sfn[i] = h < 10 ? h + '0' : h + 'A' - 10; + hex >>= 4; + } + fname->sfn[pos] = '~'; + rewind(); + while (1) { + dir = readDirCache(); + if (!dir) { + if (!getError()) { + // At EOF and name not found if no error. + goto done; + } + DBG_FAIL_MACRO; + goto fail; + } + if (dir->name[0] == FAT_NAME_FREE) { + goto done; + } + if (isFatFileOrSubdir(dir) && !memcmp(fname->sfn, dir->name, 11)) { + // Name found - try another. + break; + } + } + } + // fall inti fail - too many tries. + DBG_FAIL_MACRO; + +fail: + return false; + +done: + return true; +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag) { + bool fnameFound = false; + uint8_t lfnOrd = 0; + uint8_t freeFound = 0; + uint8_t freeNeed; + uint8_t order = 0; + uint8_t checksum = 0; + uint8_t ms10; + uint8_t nameOrd; + uint16_t curIndex; + uint16_t date; + uint16_t freeIndex = 0; + uint16_t freeTotal; + uint16_t time; + DirFat_t* dir; + const DirLfn_t* ldir; + auto vol = dirFile->m_vol; + + if (!dirFile->isDir() || isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Number of directory entries needed. + nameOrd = (fname->len + 12) / 13; + freeNeed = (fname->flags & FNAME_FLAG_NEED_LFN) ? 1 + nameOrd : 1; + dirFile->rewind(); + while (1) { + curIndex = dirFile->m_curPosition / FS_DIR_SIZE; + dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // At EOF + goto create; + } + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == FAT_NAME_FREE) { + if (freeFound == 0) { + freeIndex = curIndex; + } + if (freeFound < freeNeed) { + freeFound++; + } + if (dir->name[0] == FAT_NAME_FREE) { + goto create; + } + } else { + if (freeFound < freeNeed) { + freeFound = 0; + } + } + // skip empty slot or '.' or '..' + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + lfnOrd = 0; + } else if (isFatLongName(dir)) { + ldir = reinterpret_cast(dir); + if (!lfnOrd) { + order = ldir->order & 0X1F; + if (order != nameOrd || + (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) == 0) { + continue; + } + lfnOrd = nameOrd; + checksum = ldir->checksum; + } else if (ldir->order != --order || checksum != ldir->checksum) { + lfnOrd = 0; + continue; + } + if (order == 1) { + if (!dirFile->cmpName(curIndex + 1, fname, lfnOrd)) { + lfnOrd = 0; + } + } + } else if (isFatFileOrSubdir(dir)) { + if (lfnOrd) { + if (1 == order && lfnChecksum(dir->name) == checksum) { + goto found; + } + DBG_FAIL_MACRO; + goto fail; + } + if (!memcmp(dir->name, fname->sfn, sizeof(fname->sfn))) { + if (!(fname->flags & FNAME_FLAG_LOST_CHARS)) { + goto found; + } + fnameFound = true; + } + } else { + lfnOrd = 0; + } + } + +found: + // Don't open if create only. + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } + goto open; + +create: + // don't create unless O_CREAT and write mode + if (!(oflag & O_CREAT) || !isWriteMode(oflag)) { + DBG_WARN_MACRO; + goto fail; + } + // Keep found entries or start at current index if no free entries found. + if (freeFound == 0) { + freeIndex = curIndex; + } + while (freeFound < freeNeed) { + dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // EOF if no error. + break; + } + freeFound++; + } + // Loop handles the case of huge filename and cluster size one. + freeTotal = freeFound; + while (freeTotal < freeNeed) { + // Will fail if FAT16 root. + if (!dirFile->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + // 16-bit freeTotal needed for large cluster size. + freeTotal += vol->dirEntriesPerCluster(); + } + if (fnameFound) { + if (!dirFile->makeUniqueSfn(fname)) { + goto fail; + } + } + lfnOrd = freeNeed - 1; + curIndex = freeIndex + lfnOrd; + if (!dirFile->createLFN(curIndex, fname, lfnOrd)) { + goto fail; + } + dir = dirFile->cacheDir(curIndex); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // initialize as empty file + memset(dir, 0, sizeof(DirFat_t)); + memcpy(dir->name, fname->sfn, 11); + + // Set base-name and extension lower case bits. + dir->caseFlags = (FAT_CASE_LC_BASE | FAT_CASE_LC_EXT) & fname->flags; + + // Set timestamps. + if (FsDateTime::callback) { + // call user date/time function + FsDateTime::callback(&date, &time, &ms10); + setLe16(dir->createDate, date); + setLe16(dir->createTime, time); + dir->createTimeMs = ms10; + } else { + setLe16(dir->createDate, FS_DEFAULT_DATE); + setLe16(dir->modifyDate, FS_DEFAULT_DATE); + setLe16(dir->accessDate, FS_DEFAULT_DATE); + if (FS_DEFAULT_TIME) { + setLe16(dir->createTime, FS_DEFAULT_TIME); + setLe16(dir->modifyTime, FS_DEFAULT_TIME); + } + } + // Force write of entry to device. + vol->cacheDirty(); + +open: + // open entry in cache. + if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::parsePathName(const char* path, FatLfn_t* fname, + const char** ptr) { + size_t len = 0; + // Skip leading spaces. + while (*path == ' ') { + path++; + } + fname->begin = path; + fname->len = 0; + while (*path && !isDirSeparator(*path)) { +#if USE_UTF8_LONG_NAMES + uint32_t cp; + // Allow end = path + 4 since path is zero terminated. + path = FsUtf::mbToCp(path, path + 4, &cp); + if (!path) { + DBG_FAIL_MACRO; + goto fail; + } + len += cp <= 0XFFFF ? 1 : 2; + if (cp < 0X80 && lfnReservedChar(cp)) { + DBG_FAIL_MACRO; + goto fail; + } +#else // USE_UTF8_LONG_NAMES + uint8_t cp = *path++; + if (cp >= 0X80 || lfnReservedChar(cp)) { + DBG_FAIL_MACRO; + goto fail; + } + len++; +#endif // USE_UTF8_LONG_NAMES + if (cp != '.' && cp != ' ') { + // Need to trim trailing dots spaces. + fname->len = len; + fname->end = path; + } + } + if (!fname->len || fname->len > FAT_MAX_LFN_LENGTH) { + DBG_FAIL_MACRO; + goto fail; + } + // Advance to next path component. + for (; *path == ' ' || isDirSeparator(*path); path++) { + } + *ptr = path; + return makeSFN(fname); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::remove() { + bool last; + uint8_t checksum; + FatFile dirFile; + DirFat_t* dir; + DirLfn_t* ldir; + + // Cant' remove not open for write. + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // Cache directory entry. + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + checksum = lfnChecksum(dir->name); + + // Mark entry deleted. + dir->name[0] = FAT_NAME_DELETED; + + // Set this file closed. + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + + // Write entry to device. + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + // Done, no LFN entries. + return true; + } + if (!dirFile.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + ldir = reinterpret_cast(dirFile.cacheDir(m_dirIndex - order)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME || + order != (ldir->order & 0X1F) || checksum != ldir->checksum) { + DBG_FAIL_MACRO; + goto fail; + } + last = ldir->order & FAT_ORDER_LAST_LONG_ENTRY; + ldir->order = FAT_NAME_DELETED; + m_vol->cacheDirty(); + if (last) { + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } + } + // Fall into fail. + DBG_FAIL_MACRO; + +fail: + return false; +} +#endif // #if USE_LONG_FILE_NAMES diff --git a/third_party/sdfat/src/FatLib/FatFilePrint.cpp b/third_party/sdfat/src/FatLib/FatFilePrint.cpp new file mode 100644 index 00000000..10672594 --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatFilePrint.cpp @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#define DBG_FILE "FatFilePrint.cpp" +#include "../common/DebugMacros.h" +#include "FatLib.h" + +//------------------------------------------------------------------------------ +bool FatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) { + FatFile file; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (file.openNext(this, O_RDONLY)) { + // indent for dir level + if (!file.isHidden() || (flags & LS_A)) { + for (uint8_t i = 0; i < indent; i++) { + pr->write(' '); + } + if (flags & LS_DATE) { + file.printModifyDateTime(pr); + pr->write(' '); + } + if (flags & LS_SIZE) { + file.printFileSize(pr); + pr->write(' '); + } + file.printName(pr); + if (file.isDir()) { + pr->write('/'); + } + pr->write('\r'); + pr->write('\n'); + if ((flags & LS_R) && file.isDir()) { + file.ls(pr, flags, indent + 2); + } + } + file.close(); + } + if (getError()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::printAccessDate(print_t* pr) { + uint16_t date; + if (getAccessDate(&date)) { + return fsPrintDate(pr, date); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printCreateDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getCreateDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printModifyDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getModifyDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printFileSize(print_t* pr) { + char buf[11]; + char* ptr = buf + sizeof(buf); + *--ptr = 0; + ptr = fmtBase10(ptr, fileSize()); + while (ptr > buf) { + *--ptr = ' '; + } + return pr->write(buf); +} diff --git a/third_party/sdfat/src/FatLib/FatFileSFN.cpp b/third_party/sdfat/src/FatLib/FatFileSFN.cpp new file mode 100644 index 00000000..c3831dda --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatFileSFN.cpp @@ -0,0 +1,316 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatFileSFN.cpp" +#include "../common/DebugMacros.h" +#include "FatLib.h" +//------------------------------------------------------------------------------ +// open with filename in fname +#define SFN_OPEN_USES_CHKSUM 0 +bool FatFile::open(FatFile* dirFile, const FatSfn_t* fname, oflag_t oflag) { + uint16_t date; + uint16_t time; + uint8_t ms10; + bool emptyFound = false; +#if SFN_OPEN_USES_CHKSUM + uint8_t checksum; +#endif // SFN_OPEN_USES_CHKSUM + uint8_t lfnOrd = 0; + uint16_t emptyIndex = 0; + uint16_t index = 0; + DirFat_t* dir; + const DirLfn_t* ldir; + + dirFile->rewind(); + while (true) { + dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // At EOF if no error. + break; + } + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == FAT_NAME_FREE) { + if (!emptyFound) { + emptyIndex = index; + emptyFound = true; + } + if (dir->name[0] == FAT_NAME_FREE) { + break; + } + lfnOrd = 0; + } else if (isFatFileOrSubdir(dir)) { + if (!memcmp(fname->sfn, dir->name, 11)) { + // don't open existing file if O_EXCL + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } +#if SFN_OPEN_USES_CHKSUM + if (lfnOrd && checksum != lfnChecksum(dir->name)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // SFN_OPEN_USES_CHKSUM + if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } else { + lfnOrd = 0; + } + } else if (isFatLongName(dir)) { + ldir = reinterpret_cast(dir); + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + lfnOrd = ldir->order & 0X1F; +#if SFN_OPEN_USES_CHKSUM + checksum = ldir->checksum; +#endif // SFN_OPEN_USES_CHKSUM + } + } else { + lfnOrd = 0; + } + index++; + } + // don't create unless O_CREAT and write mode + if (!(oflag & O_CREAT) || !isWriteMode(oflag)) { + DBG_FAIL_MACRO; + goto fail; + } + if (emptyFound) { + index = emptyIndex; + } else { + if (!dirFile->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + dir = reinterpret_cast(dirFile->cacheDir(index)); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // initialize as empty file + memset(dir, 0, sizeof(DirFat_t)); + memcpy(dir->name, fname->sfn, 11); + + // Set base-name and extension lower case bits. + dir->caseFlags = (FAT_CASE_LC_BASE | FAT_CASE_LC_EXT) & fname->flags; + + // Set timestamps. + if (FsDateTime::callback) { + // call user date/time function + FsDateTime::callback(&date, &time, &ms10); + setLe16(dir->createDate, date); + setLe16(dir->createTime, time); + dir->createTimeMs = ms10; + } else { + setLe16(dir->createDate, FS_DEFAULT_DATE); + setLe16(dir->modifyDate, FS_DEFAULT_DATE); + setLe16(dir->accessDate, FS_DEFAULT_DATE); + if (FS_DEFAULT_TIME) { + setLe16(dir->createTime, FS_DEFAULT_TIME); + setLe16(dir->modifyTime, FS_DEFAULT_TIME); + } + } + // Force write of entry to device. + dirFile->m_vol->cacheDirty(); + + // open entry in cache. + return openCachedEntry(dirFile, index, oflag, 0); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openExistingSFN(const char* path) { + FatSfn_t fname; + auto vol = FatVolume::cwv(); + while (*path == '/') { + path++; + } + if (*path == 0) { + return openRoot(vol); + } + this->copy(vol->vwd()); + do { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!openSFN(&fname)) { + DBG_FAIL_MACRO; + goto fail; + } + } while (*path); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openSFN(const FatSfn_t* fname) { + DirFat_t dir; + const DirLfn_t* ldir; + auto vol = m_vol; + uint8_t lfnOrd = 0; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + while (true) { + if (read(&dir, sizeof(dir)) != sizeof(dir)) { + DBG_FAIL_MACRO; + goto fail; + } + if (dir.name[0] == 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (isFatFileOrSubdir(&dir) && memcmp(fname->sfn, dir.name, 11) == 0) { + uint16_t saveDirIndex = (m_curPosition - sizeof(dir)) >> 5; + Cluster_t saveDirCluster = m_firstCluster; + memset(this, 0, sizeof(FatFile)); + m_attributes = dir.attributes & FS_ATTRIB_COPY; + m_flags = FILE_FLAG_READ; + if (isFatFile(&dir)) { + m_attributes |= FILE_ATTR_FILE; + if (!isReadOnly()) { + m_attributes |= FS_ATTRIB_ARCHIVE; + m_flags |= FILE_FLAG_WRITE; + } + } + m_lfnOrd = lfnOrd; + m_firstCluster = static_cast(getLe16(dir.firstClusterHigh)) + << 16; + m_firstCluster |= getLe16(dir.firstClusterLow); + m_fileSize = getLe32(dir.fileSize); + m_vol = vol; + m_dirCluster = saveDirCluster; + m_dirSector = m_vol->cacheSectorNumber(); + m_dirIndex = saveDirIndex; + return true; + } else if (isFatLongName(&dir)) { + ldir = reinterpret_cast(&dir); + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + lfnOrd = ldir->order & 0X1F; + } + } else { + lfnOrd = 0; + } + } + +fail: + return false; +} +//------------------------------------------------------------------------------ +// format directory name field from a 8.3 name string +bool FatFile::parsePathName(const char* path, FatSfn_t* fname, + const char** ptr) { + uint8_t uc = 0; + uint8_t lc = 0; + uint8_t bit = FNAME_FLAG_LC_BASE; + // blank fill name and extension + for (uint8_t i = 0; i < 11; i++) { + fname->sfn[i] = ' '; + } + for (uint8_t i = 0, n = 7;; path++) { + uint8_t c = *path; + if (c == 0 || isDirSeparator(c)) { + // Done. + break; + } + if (c == '.' && n == 7) { + n = 10; // max index for full 8.3 name + i = 8; // place for extension + + // bit for extension. + bit = FNAME_FLAG_LC_EXT; + } else { + if (sfnReservedChar(c) || i > n) { + DBG_FAIL_MACRO; + goto fail; + } + if ('a' <= c && c <= 'z') { + c += 'A' - 'a'; + lc |= bit; + } else if ('A' <= c && c <= 'Z') { + uc |= bit; + } + fname->sfn[i++] = c; + } + } + // must have a file name, extension is optional + if (fname->sfn[0] == ' ') { + DBG_FAIL_MACRO; + goto fail; + } + // Set base-name and extension bits. + fname->flags = (lc & uc) ? 0 : lc; + while (isDirSeparator(*path)) { + path++; + } + *ptr = path; + return true; + +fail: + return false; +} +#if !USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +bool FatFile::remove() { + DirFat_t* dir; + // Can't remove if LFN or not open for write. + if (!isWritable() || isLFN()) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // Cache directory entry. + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // Mark entry deleted. + dir->name[0] = FAT_NAME_DELETED; + + // Set this file closed. + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + + // Write entry to device. + return m_vol->cacheSync(); + +fail: + return false; +} +#endif // !USE_LONG_FILE_NAMES diff --git a/third_party/sdfat/src/FatLib/FatFormatter.cpp b/third_party/sdfat/src/FatLib/FatFormatter.cpp new file mode 100644 index 00000000..8573b191 --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatFormatter.cpp @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FatLib.h" +// Set nonzero to use calculated CHS in MBR. Should not be required. +#define USE_LBA_TO_CHS 1 + +// Constants for file system structure optimized for flash. +uint16_t const BU16 = 128; +uint16_t const BU32 = 8192; +// Assume 512 byte sectors. +const uint16_t BYTES_PER_SECTOR = 512; +const uint16_t SECTORS_PER_MB = 0X100000 / BYTES_PER_SECTOR; +const uint16_t FAT16_ROOT_ENTRY_COUNT = 512; +const uint16_t FAT16_ROOT_SECTOR_COUNT = + 32 * FAT16_ROOT_ENTRY_COUNT / BYTES_PER_SECTOR; +//------------------------------------------------------------------------------ +#define PRINT_FORMAT_PROGRESS 1 +#if !PRINT_FORMAT_PROGRESS +#define writeMsg(str) +#elif defined(__AVR__) +#define writeMsg(str) \ + if (m_pr) m_pr->print(F(str)) +#else // PRINT_FORMAT_PROGRESS +#define writeMsg(str) \ + if (m_pr) m_pr->write(str) +#endif // PRINT_FORMAT_PROGRESS +//------------------------------------------------------------------------------ +bool FatFormatter::format(FsBlockDevice* dev, uint8_t* secBuf, print_t* pr) { + bool rtn; + m_dev = dev; + m_secBuf = secBuf; + m_pr = pr; + m_sectorCount = m_dev->sectorCount(); + m_capacityMB = (m_sectorCount + SECTORS_PER_MB - 1) / SECTORS_PER_MB; + + if (m_capacityMB <= 6) { + writeMsg("Card is too small.\r\n"); + return false; + } else if (m_capacityMB <= 16) { + m_sectorsPerCluster = 2; + } else if (m_capacityMB <= 32) { + m_sectorsPerCluster = 4; + } else if (m_capacityMB <= 64) { + m_sectorsPerCluster = 8; + } else if (m_capacityMB <= 128) { + m_sectorsPerCluster = 16; + } else if (m_capacityMB <= 1024) { + m_sectorsPerCluster = 32; + } else if (m_capacityMB <= 32768) { + m_sectorsPerCluster = 64; + } else { + // SDXC cards + m_sectorsPerCluster = 128; + } + rtn = m_sectorCount < 0X400000 ? makeFat16() : makeFat32(); + if (rtn) { + writeMsg("Format Done\r\n"); + } else { + writeMsg("Format Failed\r\n"); + } + return rtn; +} +//------------------------------------------------------------------------------ +bool FatFormatter::initFatDir(uint8_t fatType, Sector_t sectorCount) { + size_t n; + memset(m_secBuf, 0, BYTES_PER_SECTOR); + writeMsg("Writing FAT "); + for (uint32_t i = 1; i < sectorCount; i++) { + if (!m_dev->writeSector(m_fatStart + i, m_secBuf)) { + return false; + } + if ((i % (sectorCount / 32)) == 0) { + writeMsg("."); + } + } + writeMsg("\r\n"); + // Allocate reserved clusters and root for FAT32. + m_secBuf[0] = 0XF8; + n = fatType == 16 ? 4 : 12; + for (size_t i = 1; i < n; i++) { + m_secBuf[i] = 0XFF; + } + return m_dev->writeSector(m_fatStart, m_secBuf) && + m_dev->writeSector(m_fatStart + m_fatSize, m_secBuf); +} +//------------------------------------------------------------------------------ +void FatFormatter::initPbs() { + PbsFat_t* pbs = reinterpret_cast(m_secBuf); + memset(m_secBuf, 0, BYTES_PER_SECTOR); + pbs->jmpInstruction[0] = 0XEB; + pbs->jmpInstruction[1] = 0X76; + pbs->jmpInstruction[2] = 0X90; + for (uint8_t i = 0; i < sizeof(pbs->oemName); i++) { + pbs->oemName[i] = ' '; + } + setLe16(pbs->bpb.bpb16.bytesPerSector, BYTES_PER_SECTOR); + pbs->bpb.bpb16.sectorsPerCluster = m_sectorsPerCluster; + setLe16(pbs->bpb.bpb16.reservedSectorCount, m_reservedSectorCount); + pbs->bpb.bpb16.fatCount = 2; + // skip rootDirEntryCount + // skip totalSectors16 + pbs->bpb.bpb16.mediaType = 0XF8; + // skip sectorsPerFat16 + // skip sectorsPerTrack + // skip headCount + setLe32(pbs->bpb.bpb16.hidddenSectors, m_startSector); + setLe32(pbs->bpb.bpb16.totalSectors32, m_totalSectors); + // skip rest of bpb + setLe16(pbs->signature, PBR_SIGNATURE); +} +//------------------------------------------------------------------------------ +bool FatFormatter::makeFat16() { + uint32_t nc; + PbsFat_t* pbs = reinterpret_cast(m_secBuf); + + for (m_dataStart = 2 * BU16;; m_dataStart += BU16) { + nc = (m_sectorCount - m_dataStart) / m_sectorsPerCluster; + m_fatSize = (nc + 2 + (BYTES_PER_SECTOR / 2) - 1) / (BYTES_PER_SECTOR / 2); + uint32_t r = BU16 + 1 + 2 * m_fatSize + FAT16_ROOT_SECTOR_COUNT; + if (m_dataStart >= r) { + m_startSector = m_dataStart - r + BU16; + break; + } + } + // check valid cluster count for FAT16 volume + if (nc < 4085 || nc >= 65525) { + writeMsg("Bad cluster count\r\n"); + return false; + } + m_reservedSectorCount = 1; + m_fatStart = m_startSector + m_reservedSectorCount; + m_totalSectors = + nc * m_sectorsPerCluster + 2 * m_fatSize + m_reservedSectorCount + 32; + if (m_totalSectors < 65536) { + m_partType = 0X04; + } else { + m_partType = 0X06; + } + // write MBR + if (!writeMbr()) { + return false; + } + initPbs(); + setLe16(pbs->bpb.bpb16.rootDirEntryCount, FAT16_ROOT_ENTRY_COUNT); + setLe16(pbs->bpb.bpb16.sectorsPerFat16, m_fatSize); + pbs->bpb.bpb16.physicalDriveNumber = 0X80; + pbs->bpb.bpb16.extSignature = EXTENDED_BOOT_SIGNATURE; + setLe32(pbs->bpb.bpb16.volumeSerialNumber, 1234567); + for (size_t i = 0; i < sizeof(pbs->bpb.bpb16.volumeLabel); i++) { + pbs->bpb.bpb16.volumeLabel[i] = ' '; + } + pbs->bpb.bpb16.volumeType[0] = 'F'; + pbs->bpb.bpb16.volumeType[1] = 'A'; + pbs->bpb.bpb16.volumeType[2] = 'T'; + pbs->bpb.bpb16.volumeType[3] = '1'; + pbs->bpb.bpb16.volumeType[4] = '6'; + if (!m_dev->writeSector(m_startSector, m_secBuf)) { + return false; + } + return initFatDir(16, m_dataStart - m_fatStart); +} +//------------------------------------------------------------------------------ +bool FatFormatter::makeFat32() { + uint32_t nc; + PbsFat_t* pbs = reinterpret_cast(m_secBuf); + FsInfo_t* fsi = reinterpret_cast(m_secBuf); + + m_startSector = BU32; + for (m_dataStart = 2 * BU32;; m_dataStart += BU32) { + nc = (m_sectorCount - m_dataStart) / m_sectorsPerCluster; + m_fatSize = (nc + 2 + (BYTES_PER_SECTOR / 4) - 1) / (BYTES_PER_SECTOR / 4); + uint32_t r = m_startSector + 9 + 2 * m_fatSize; + if (m_dataStart >= r) { + break; + } + } + // error if too few clusters in FAT32 volume + if (nc < 65525) { + writeMsg("Bad cluster count\r\n"); + return false; + } + m_reservedSectorCount = m_dataStart - m_startSector - 2 * m_fatSize; + m_fatStart = m_startSector + m_reservedSectorCount; + m_totalSectors = nc * m_sectorsPerCluster + m_dataStart - m_startSector; + // type depends on address of end sector + // max CHS has lba = 16450560 = 1024*255*63 + if ((m_startSector + m_totalSectors) <= 16450560) { + // FAT32 with CHS and LBA + m_partType = 0X0B; + } else { + // FAT32 with only LBA + m_partType = 0X0C; + } + if (!writeMbr()) { + return false; + } + initPbs(); + setLe32(pbs->bpb.bpb32.sectorsPerFat32, m_fatSize); + setLe32(pbs->bpb.bpb32.fat32RootCluster, 2); + setLe16(pbs->bpb.bpb32.fat32FSInfoSector, 1); + setLe16(pbs->bpb.bpb32.fat32BackBootSector, 6); + pbs->bpb.bpb32.physicalDriveNumber = 0X80; + pbs->bpb.bpb32.extSignature = EXTENDED_BOOT_SIGNATURE; + setLe32(pbs->bpb.bpb32.volumeSerialNumber, 1234567); + for (size_t i = 0; i < sizeof(pbs->bpb.bpb32.volumeLabel); i++) { + pbs->bpb.bpb32.volumeLabel[i] = ' '; + } + pbs->bpb.bpb32.volumeType[0] = 'F'; + pbs->bpb.bpb32.volumeType[1] = 'A'; + pbs->bpb.bpb32.volumeType[2] = 'T'; + pbs->bpb.bpb32.volumeType[3] = '3'; + pbs->bpb.bpb32.volumeType[4] = '2'; + if (!m_dev->writeSector(m_startSector, m_secBuf) || + !m_dev->writeSector(m_startSector + 6, m_secBuf)) { + return false; + } + // write extra boot area and backup + memset(m_secBuf, 0, BYTES_PER_SECTOR); + setLe32(fsi->trailSignature, FSINFO_TRAIL_SIGNATURE); + if (!m_dev->writeSector(m_startSector + 2, m_secBuf) || + !m_dev->writeSector(m_startSector + 8, m_secBuf)) { + return false; + } + // write FSINFO sector and backup + setLe32(fsi->leadSignature, FSINFO_LEAD_SIGNATURE); + setLe32(fsi->structSignature, FSINFO_STRUCT_SIGNATURE); + setLe32(fsi->freeCount, 0XFFFFFFFF); + setLe32(fsi->nextFree, 0XFFFFFFFF); + if (!m_dev->writeSector(m_startSector + 1, m_secBuf) || + !m_dev->writeSector(m_startSector + 7, m_secBuf)) { + return false; + } + return initFatDir(32, 2 * m_fatSize + m_sectorsPerCluster); +} +//------------------------------------------------------------------------------ +bool FatFormatter::writeMbr() { + memset(m_secBuf, 0, BYTES_PER_SECTOR); + MbrSector_t* mbr = reinterpret_cast(m_secBuf); + +#if USE_LBA_TO_CHS + lbaToMbrChs(mbr->part->beginCHS, m_capacityMB, m_startSector); + lbaToMbrChs(mbr->part->endCHS, m_capacityMB, + m_startSector + m_totalSectors - 1); +#else // USE_LBA_TO_CHS + mbr->part->beginCHS[0] = 1; + mbr->part->beginCHS[1] = 1; + mbr->part->beginCHS[2] = 0; + mbr->part->endCHS[0] = 0XFE; + mbr->part->endCHS[1] = 0XFF; + mbr->part->endCHS[2] = 0XFF; +#endif // USE_LBA_TO_CHS + + mbr->part->type = m_partType; + setLe32(mbr->part->startSector, m_startSector); + setLe32(mbr->part->totalSectors, m_totalSectors); + setLe16(mbr->signature, MBR_SIGNATURE); + return m_dev->writeSector(0, m_secBuf); +} diff --git a/third_party/sdfat/src/FatLib/FatFormatter.h b/third_party/sdfat/src/FatLib/FatFormatter.h new file mode 100644 index 00000000..3fe81c46 --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatFormatter.h @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "../common/FsBlockDevice.h" +#include "../common/SysCall.h" +/** + * \class FatFormatter + * \brief Format a FAT volume. + */ +class FatFormatter { + public: + /** Constructor. */ + FatFormatter() = default; // cppcheck-suppress uninitMemberVar + /** + * Format a FAT volume. + * + * \param[in] dev Block device for volume. + * \param[in] secBuffer buffer for writing to volume. + * \param[in] pr Print device for progress output. + * + * \return true for success or false for failure. + */ + bool format(FsBlockDevice* dev, uint8_t* secBuffer, print_t* pr = nullptr); + + private: + bool initFatDir(uint8_t fatType, Sector_t sectorCount); + void initPbs(); + bool makeFat16(); + bool makeFat32(); + bool writeMbr(); + uint32_t m_capacityMB; + uint32_t m_dataStart; + uint32_t m_fatSize; + uint32_t m_fatStart; + Sector_t m_startSector; + Sector_t m_sectorCount; + Sector_t m_totalSectors; + FsBlockDevice* m_dev; + print_t* m_pr; + uint8_t* m_secBuf; + uint16_t m_reservedSectorCount; + uint8_t m_partType; + uint8_t m_sectorsPerCluster; +}; diff --git a/third_party/sdfat/src/FatLib/FatLib.h b/third_party/sdfat/src/FatLib/FatLib.h new file mode 100644 index 00000000..c1173787 --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatLib.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "FatFormatter.h" +#include "FatVolume.h" diff --git a/third_party/sdfat/src/FatLib/FatName.cpp b/third_party/sdfat/src/FatLib/FatName.cpp new file mode 100644 index 00000000..8d04823f --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatName.cpp @@ -0,0 +1,356 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatName.cpp" +#include "../common/DebugMacros.h" +#include "../common/FsUtf.h" +#include "FatLib.h" +//------------------------------------------------------------------------------ +uint16_t FatFile::getLfnChar(const DirLfn_t* ldir, uint8_t i) { + if (i < 5) { + return getLe16(ldir->unicode1 + 2 * i); + } else if (i < 11) { + return getLe16(ldir->unicode2 + 2 * (i - 5)); + } else if (i < 13) { + return getLe16(ldir->unicode3 + 2 * (i - 11)); + } + DBG_HALT_IF(i >= 13); + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::getName(char* name, size_t size) { +#if !USE_LONG_FILE_NAMES + return getSFN(name, size); +#elif USE_UTF8_LONG_NAMES + return getName8(name, size); +#else + return getName7(name, size); +#endif // !USE_LONG_FILE_NAMES +} +//------------------------------------------------------------------------------ +size_t FatFile::getName7(char* name, size_t size) { + FatFile dir; + const DirLfn_t* ldir; + size_t n = 0; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + return getSFN(name, size); + } + if (!dir.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME || + order != (ldir->order & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t i = 0; i < 13; i++) { + uint16_t c = getLfnChar(ldir, i); + if (c == 0) { + goto done; + } + if ((n + 1) >= size) { + DBG_FAIL_MACRO; + goto fail; + } + name[n++] = c >= 0X7F ? '?' : c; + } + } +done: + name[n] = 0; + return n; + +fail: + name[0] = '\0'; + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::getName8(char* name, size_t size) { + const char* end = name + size; + char* str = name; + char* ptr; + FatFile dir; + const DirLfn_t* ldir; + uint16_t hs = 0; + uint32_t cp; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + return getSFN(name, size); + } + if (!dir.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME || + order != (ldir->order & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t i = 0; i < 13; i++) { + uint16_t c = getLfnChar(ldir, i); + if (hs) { + if (!FsUtf::isLowSurrogate(c)) { + DBG_FAIL_MACRO; + goto fail; + } + cp = FsUtf::u16ToCp(hs, c); + hs = 0; + } else if (!FsUtf::isSurrogate(c)) { + if (c == 0) { + goto done; + } + cp = c; + } else if (FsUtf::isHighSurrogate(c)) { + hs = c; + continue; + } else { + DBG_FAIL_MACRO; + goto fail; + } + // Save space for zero byte. + ptr = FsUtf::cpToMb(cp, str, end - 1); + if (!ptr) { + DBG_FAIL_MACRO; + goto fail; + } + str = ptr; + } + } +done: + *str = '\0'; + return str - name; + +fail: + *name = 0; + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::getSFN(char* name, size_t size) { + char c; + uint8_t j = 0; + uint8_t lcBit = FAT_CASE_LC_BASE; + const uint8_t* ptr; + const DirFat_t* dir; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isRoot()) { + if (size < 2) { + DBG_FAIL_MACRO; + goto fail; + } + name[0] = '/'; + name[1] = '\0'; + return 1; + } + // cache entry + dir = cacheDirEntry(FsCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + ptr = dir->name; + // format name + for (uint8_t i = 0; i < 12; i++) { + if (i == 8) { + if (*ptr == ' ') { + break; + } + lcBit = FAT_CASE_LC_EXT; + c = '.'; + } else { + c = *ptr++; + if ('A' <= c && c <= 'Z' && (lcBit & dir->caseFlags)) { + c += 'a' - 'A'; + } + if (c == ' ') { + continue; + } + } + if ((j + 1u) >= size) { + DBG_FAIL_MACRO; + goto fail; + } + name[j++] = c; + } + name[j] = '\0'; + return j; + +fail: + name[0] = '\0'; + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printName(print_t* pr) { +#if !USE_LONG_FILE_NAMES + return printSFN(pr); +#elif USE_UTF8_LONG_NAMES + return printName8(pr); +#else // USE_LONG_FILE_NAMES + return printName7(pr); +#endif // !USE_LONG_FILE_NAMES +} +//------------------------------------------------------------------------------ +size_t FatFile::printName7(print_t* pr) { + FatFile dir; + const DirLfn_t* ldir; + size_t n = 0; + uint8_t buf[13]; + uint8_t i; + + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + return printSFN(pr); + } + if (!dir.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME || + order != (ldir->order & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + for (i = 0; i < 13; i++) { + uint16_t u = getLfnChar(ldir, i); + if (u == 0) { + // End of name. + break; + } + buf[i] = u < 0X7F ? u : '?'; + n++; + } + pr->write(buf, i); + } + return n; + +fail: + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printName8(print_t* pr) { + FatFile dir; + const DirLfn_t* ldir; + uint16_t hs = 0; + uint32_t cp; + size_t n = 0; + char buf[5]; + const char* end = buf + sizeof(buf); + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + return printSFN(pr); + } + if (!dir.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME || + order != (ldir->order & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t i = 0; i < 13; i++) { + uint16_t c = getLfnChar(ldir, i); + if (hs) { + if (!FsUtf::isLowSurrogate(c)) { + DBG_FAIL_MACRO; + goto fail; + } + cp = FsUtf::u16ToCp(hs, c); + hs = 0; + } else if (!FsUtf::isSurrogate(c)) { + if (c == 0) { + break; + } + cp = c; + } else if (FsUtf::isHighSurrogate(c)) { + hs = c; + continue; + } else { + DBG_FAIL_MACRO; + goto fail; + } + const char* str = FsUtf::cpToMb(cp, buf, end); + if (!str) { + DBG_FAIL_MACRO; + goto fail; + } + n += pr->write(reinterpret_cast(buf), str - buf); + } + } + return n; + +fail: + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printSFN(print_t* pr) { + char name[13]; + if (!getSFN(name, sizeof(name))) { + DBG_FAIL_MACRO; + goto fail; + } + return pr->write(name); + +fail: + return 0; +} diff --git a/third_party/sdfat/src/FatLib/FatPartition.cpp b/third_party/sdfat/src/FatLib/FatPartition.cpp new file mode 100644 index 00000000..942ae90b --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatPartition.cpp @@ -0,0 +1,507 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#define DBG_FILE "FatPartition.cpp" +#include "../common/DebugMacros.h" +#include "FatLib.h" +//------------------------------------------------------------------------------ +bool FatPartition::allocateCluster(Cluster_t current, Cluster_t* next) { + Cluster_t find; + bool setStart; + if (m_allocSearchStart < current) { + // Try to keep file contiguous. Start just after current cluster. + find = current; + setStart = false; + } else { + find = m_allocSearchStart; + setStart = true; + } + while (1) { + find++; + if (find > m_lastCluster) { + if (setStart) { + // Can't find space, checked all clusters. + DBG_FAIL_MACRO; + goto fail; + } + find = m_allocSearchStart; + setStart = true; + continue; + } + if (find == current) { + // Can't find space, already searched clusters after current. + DBG_FAIL_MACRO; + goto fail; + } + uint32_t f; + int8_t fg = fatGet(find, &f); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg && f == 0) { + break; + } + } + if (setStart) { + m_allocSearchStart = find; + } + // Mark end of chain. + if (!fatPutEOC(find)) { + DBG_FAIL_MACRO; + goto fail; + } + if (current) { + // Link clusters. + if (!fatPut(current, find)) { + DBG_FAIL_MACRO; + goto fail; + } + } + updateFreeClusterCount(-1); + *next = find; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// find a contiguous group of clusters +bool FatPartition::allocContiguous(uint32_t count, Cluster_t* firstCluster) { + // flag to save place to start next search + bool setStart = true; + // start of group + Cluster_t bgnCluster; + // end of group + Cluster_t endCluster; + // Start at cluster after last allocated cluster. + endCluster = bgnCluster = m_allocSearchStart + 1; + + // search the FAT for free clusters + while (1) { + if (endCluster > m_lastCluster) { + // Can't find space. + DBG_FAIL_MACRO; + goto fail; + } + uint32_t f; + int8_t fg = fatGet(endCluster, &f); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (f || fg == 0) { + // don't update search start if unallocated clusters before endCluster. + if (bgnCluster != endCluster) { + setStart = false; + } + // cluster in use try next cluster as bgnCluster + bgnCluster = endCluster + 1; + } else if ((endCluster - bgnCluster + 1) == count) { + // done - found space + break; + } + endCluster++; + } + // Remember possible next free cluster. + if (setStart) { + m_allocSearchStart = endCluster; + } + // mark end of chain + if (!fatPutEOC(endCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // link clusters + while (endCluster > bgnCluster) { + if (!fatPut(endCluster - 1, endCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + endCluster--; + } + // Maintain count of free clusters. + updateFreeClusterCount(-count); + + // return first cluster number to caller + *firstCluster = bgnCluster; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// Fetch a FAT entry - return -1 error, 0 EOC, else 1. +int8_t FatPartition::fatGet(Cluster_t cluster, Cluster_t* value) { + Sector_t sector; + uint32_t next; + const uint8_t* pc; + + // error if reserved cluster of beyond FAT + if (cluster < 2 || cluster > m_lastCluster) { + DBG_FAIL_MACRO; + goto fail; + } + + if (fatType() == 32) { + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint16_t offset = (cluster << 2) & m_sectorMask; + next = getLe32(pc + offset); + } else if (fatType() == 16) { + cluster &= 0XFFFF; + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1)); + pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint16_t offset = (cluster << 1) & m_sectorMask; + next = getLe16(pc + offset); + } else if (FAT12_SUPPORT && fatType() == 12) { + uint16_t index = cluster; + index += index >> 1; + sector = m_fatStartSector + (index >> m_bytesPerSectorShift); + pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index &= m_sectorMask; + uint16_t tmp = pc[index]; + index++; + if (index == m_bytesPerSector) { + pc = fatCachePrepare(sector + 1, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index = 0; + } + tmp |= pc[index] << 8; + next = cluster & 1 ? tmp >> 4 : tmp & 0XFFF; + } else { + DBG_FAIL_MACRO; + goto fail; + } + if (isEOC(next)) { + return 0; + } + *value = next; + return 1; + +fail: + return -1; +} +//------------------------------------------------------------------------------ +// Store a FAT entry +bool FatPartition::fatPut(Cluster_t cluster, Cluster_t value) { + Sector_t sector; + uint8_t* pc; + + // error if reserved cluster of beyond FAT + if (cluster < 2 || cluster > m_lastCluster) { + DBG_FAIL_MACRO; + goto fail; + } + + if (fatType() == 32) { + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint16_t offset = (cluster << 2) & m_sectorMask; + setLe32(pc + offset, value); + return true; + } + + if (fatType() == 16) { + cluster &= 0XFFFF; + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1)); + pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint16_t offset = (cluster << 1) & m_sectorMask; + setLe16(pc + offset, value); + return true; + } + + if (FAT12_SUPPORT && fatType() == 12) { + uint16_t index = cluster; + index += index >> 1; + sector = m_fatStartSector + (index >> m_bytesPerSectorShift); + pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index &= m_sectorMask; + uint8_t tmp = value; + if (cluster & 1) { + tmp = (pc[index] & 0XF) | tmp << 4; + } + pc[index] = tmp; + + index++; + if (index == m_bytesPerSector) { + sector++; + index = 0; + pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmp = value >> 4; + if (!(cluster & 1)) { + tmp = ((pc[index] & 0XF0)) | tmp >> 4; + } + pc[index] = tmp; + return true; + } else { + DBG_FAIL_MACRO; + goto fail; + } + +fail: + return false; +} +//------------------------------------------------------------------------------ +// free a cluster chain +bool FatPartition::freeChain(Cluster_t cluster) { + uint32_t next; + int8_t fg; + do { + fg = fatGet(cluster, &next); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + // free cluster + if (!fatPut(cluster, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + // Add one to count of free clusters. + updateFreeClusterCount(1); + if (cluster < m_allocSearchStart) { + m_allocSearchStart = cluster - 1; + } + cluster = next; + } while (fg); + + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +int32_t FatPartition::freeClusterCount() { +#if MAINTAIN_FREE_CLUSTER_COUNT + if (m_freeClusterCount >= 0) { + return m_freeClusterCount; + } +#endif // MAINTAIN_FREE_CLUSTER_COUNT + uint32_t free = 0; + Sector_t sector; + uint32_t todo = m_lastCluster + 1; + uint16_t n; + + if (FAT12_SUPPORT && fatType() == 12) { + for (unsigned i = 2; i < todo; i++) { + uint32_t c; + int8_t fg = fatGet(i, &c); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg && c == 0) { + free++; + } + } + } else if (fatType() == 16 || fatType() == 32) { + sector = m_fatStartSector; + while (todo) { + uint8_t* pc = fatCachePrepare(sector++, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + n = fatType() == 16 ? m_bytesPerSector / 2 : m_bytesPerSector / 4; + if (todo < n) { + n = todo; + } + if (fatType() == 16) { + const uint16_t* p16 = reinterpret_cast(pc); + for (uint16_t i = 0; i < n; i++) { + if (p16[i] == 0) { + free++; + } + } + } else { + const uint32_t* p32 = reinterpret_cast(pc); + for (uint16_t i = 0; i < n; i++) { + if (p32[i] == 0) { + free++; + } + } + } + todo -= n; + } + } else { + // invalid FAT type + DBG_FAIL_MACRO; + goto fail; + } + setFreeClusterCount(free); + return free; + +fail: + return -1; +} +//------------------------------------------------------------------------------ +bool FatPartition::init(FsBlockDevice* dev, uint8_t part, + Sector_t startSector) { + Cluster_t countOfClusters; + Sector_t totalSectors; + m_blockDev = dev; + pbs_t* pbs; + const BpbFat32_t* bpb; + const MbrSector_t* mbr; + uint8_t tmp; + m_fatType = 0; + m_allocSearchStart = 1; + m_cache.init(dev); +#if USE_SEPARATE_FAT_CACHE + m_fatCache.init(dev); +#endif // USE_SEPARATE_FAT_CACHE + // if part == 0 assume super floppy with FAT boot sector in sector zero + // if part > 0 assume mbr volume with partition table + if (part) { + if (part > 4) { + DBG_FAIL_MACRO; + goto fail; + } + mbr = reinterpret_cast( + dataCachePrepare(0, FsCache::CACHE_FOR_READ)); + if (!mbr) { + DBG_FAIL_MACRO; + goto fail; + } + const MbrPart_t* mp = mbr->part + part - 1; + if (mp->type == 0 || (mp->boot != 0 && mp->boot != 0X80)) { + DBG_FAIL_MACRO; + goto fail; + } + startSector = getLe32(mp->startSector); + } + pbs = reinterpret_cast( + dataCachePrepare(startSector, FsCache::CACHE_FOR_READ)); + if (!pbs) { + DBG_FAIL_MACRO; + goto fail; + } + bpb = reinterpret_cast(pbs->bpb); + if (getLe16(bpb->bytesPerSector) != m_bytesPerSector) { + DBG_FAIL_MACRO; + goto fail; + } + if (bpb->fatCount != 1 && bpb->fatCount != 2) { + DBG_FAIL_MACRO; + goto fail; + } + m_fatCount = bpb->fatCount; + m_sectorsPerCluster = bpb->sectorsPerCluster; + m_clusterSectorMask = m_sectorsPerCluster - 1; + // determine shift that is same as multiply by m_sectorsPerCluster + m_sectorsPerClusterShift = 0; + for (tmp = 1; m_sectorsPerCluster != tmp; tmp <<= 1) { + if (tmp == 0) { + DBG_FAIL_MACRO; + goto fail; + } + m_sectorsPerClusterShift++; + } + m_sectorsPerFat = getLe16(bpb->sectorsPerFat16); + if (m_sectorsPerFat == 0) { + m_sectorsPerFat = getLe32(bpb->sectorsPerFat32); + } + m_fatStartSector = startSector + getLe16(bpb->reservedSectorCount); + + // count for FAT12/FAT16 zero for FAT32 + m_rootDirEntryCount = getLe16(bpb->rootDirEntryCount); + + // directory start for FAT12/FAT16 dataStart for FAT32 + m_rootDirStart = m_fatStartSector + m_fatCount * m_sectorsPerFat; + // data start for FAT16 and FAT32 + m_dataStartSector = + m_rootDirStart + + ((FS_DIR_SIZE * m_rootDirEntryCount + m_bytesPerSector - 1) / + m_bytesPerSector); + + // total sectors for FAT16 or FAT32 + totalSectors = getLe16(bpb->totalSectors16); + if (totalSectors == 0) { + totalSectors = getLe32(bpb->totalSectors32); + } + // total data sectors + countOfClusters = totalSectors - (m_dataStartSector - startSector); + + // divide by cluster size to get cluster count + countOfClusters >>= m_sectorsPerClusterShift; + m_lastCluster = countOfClusters + 1; + + // Indicate unknown number of free clusters. + setFreeClusterCount(-1); + // FAT type is determined by cluster count + if (countOfClusters < 4085) { + m_fatType = 12; + if (!FAT12_SUPPORT) { + DBG_FAIL_MACRO; + goto fail; + } + } else if (countOfClusters < 65525) { + m_fatType = 16; + } else { + m_rootDirStart = getLe32(bpb->fat32RootCluster); + m_fatType = 32; + } + m_cache.setMirrorOffset(m_sectorsPerFat); +#if USE_SEPARATE_FAT_CACHE + m_fatCache.setMirrorOffset(m_sectorsPerFat); +#endif // USE_SEPARATE_FAT_CACHE + return true; + +fail: + return false; +} diff --git a/third_party/sdfat/src/FatLib/FatPartition.h b/third_party/sdfat/src/FatLib/FatPartition.h new file mode 100644 index 00000000..b6b1c741 --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatPartition.h @@ -0,0 +1,238 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief FatPartition class + */ +#include + +#include "../common/FsBlockDevice.h" +#include "../common/FsCache.h" +#include "../common/FsStructs.h" +#include "../common/SysCall.h" + +/** Type for FAT12 partition */ +const uint8_t FAT_TYPE_FAT12 = 12; + +/** Type for FAT12 partition */ +const uint8_t FAT_TYPE_FAT16 = 16; + +/** Type for FAT12 partition */ +const uint8_t FAT_TYPE_FAT32 = 32; + +//============================================================================== +/** + * \class FatPartition + * \brief Access FAT16 and FAT32 partitions on raw file devices. + */ +class FatPartition { + public: + /** Create an instance of FatPartition + */ + FatPartition() = default; // cppcheck-suppress uninitMemberVar + + /** \return The shift count required to multiply by bytesPerCluster. */ + uint8_t bytesPerClusterShift() const { + return m_sectorsPerClusterShift + m_bytesPerSectorShift; + } + /** \return Number of bytes in a cluster. */ + uint16_t bytesPerCluster() const { + return m_bytesPerSector << m_sectorsPerClusterShift; + } + /** \return Number of bytes per sector. */ + uint16_t bytesPerSector() const { return m_bytesPerSector; } + /** \return The shift count required to multiply by bytesPerCluster. */ + uint8_t bytesPerSectorShift() const { return m_bytesPerSectorShift; } + /** \return Number of directory entries per cluster. */ + uint16_t dirEntriesPerCluster() const { + return m_sectorsPerCluster * (m_bytesPerSector / FS_DIR_SIZE); + } + /** \return Mask for sector offset. */ + uint16_t sectorMask() const { return m_sectorMask; } + /** \return The volume's cluster size in sectors. */ + uint8_t sectorsPerCluster() const { return m_sectorsPerCluster; } + /** \return The number of sectors in one FAT. */ + Sector_t sectorsPerFat() const { return m_sectorsPerFat; } + /** Clear the cache and returns a pointer to the cache. Not for normal apps. + * \return A pointer to the cache buffer or zero if an error occurs. + */ + uint8_t* cacheClear() { return m_cache.clear(); } + /** \return The total number of clusters in the volume. */ + Cluster_t clusterCount() const { return m_lastCluster - 1; } + /** \return The shift count required to multiply by sectorsPerCluster. */ + uint8_t sectorsPerClusterShift() const { return m_sectorsPerClusterShift; } + /** \return The logical sector number for the start of file data. */ + Sector_t dataStartSector() const { return m_dataStartSector; } + /** End access to volume + * \return pointer to sector size buffer for format. + */ + uint8_t* end() { + m_fatType = 0; + return cacheClear(); + } + /** \return The number of File Allocation Tables. */ + uint8_t fatCount() const { return m_fatCount; } + /** \return The logical sector number for the start of the first FAT. */ + Sector_t fatStartSector() const { return m_fatStartSector; } + /** \return The FAT type of the volume. Values are 12, 16 or 32. */ + uint8_t fatType() const { return m_fatType; } + /** \return free cluster count or -1 if an error occurs. */ + int32_t freeClusterCount(); + /** Initialize a FAT partition. + * + * \param[in] dev FsBlockDevice for this partition. + * \param[in] part The partition to be used. Legal values for \a part are + * 1-4 to use the corresponding partition on a device formatted with + * a MBR, Master Boot Record, or zero if the device is formatted as + * a super floppy with the FAT boot sector in sector startSector. + * \param[in] startSector location of volume if part is zero. + * + * \return true for success or false for failure. + */ + bool init(FsBlockDevice* dev, uint8_t part = 1, Sector_t startSector = 0); + /** \return The number of entries in the root directory for FAT16 volumes. */ + uint16_t rootDirEntryCount() const { return m_rootDirEntryCount; } + /** \return The logical sector number for the start of the root directory + on FAT16 volumes or the first cluster number on FAT32 volumes. */ + Cluster_t rootDirStart() const { return m_rootDirStart; } + /** \return The number of sectors in the volume */ + Sector_t volumeSectorCount() const { + return sectorsPerCluster() * clusterCount(); + } + /** Debug access to FAT table + * + * \param[in] n cluster number. + * \param[out] v value of entry + * \return -1 error, 0 EOC, else 1. + */ + int8_t dbgFat(uint32_t n, uint32_t* v) { return fatGet(n, v); } + /** + * Check for FsBlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy() { return m_blockDev->isBusy(); } + //---------------------------------------------------------------------------- +#ifndef DOXYGEN_SHOULD_SKIP_THIS + bool dmpDirSector(print_t* pr, Sector_t sector); + void dmpFat(print_t* pr, uint32_t start, uint32_t count); + bool dmpRootDir(print_t* pr, uint32_t n = 0); + void dmpSector(print_t* pr, Sector_t sector, uint8_t bits = 8); +#endif // DOXYGEN_SHOULD_SKIP_THIS + //---------------------------------------------------------------------------- + private: + /** FatFile allowed access to private members. */ + friend class FatFile; + //---------------------------------------------------------------------------- + static const uint8_t m_bytesPerSectorShift = 9; + static const uint16_t m_bytesPerSector = 1 << m_bytesPerSectorShift; + static const uint16_t m_sectorMask = m_bytesPerSector - 1; + //---------------------------------------------------------------------------- + FsBlockDevice* m_blockDev; // sector device + uint8_t m_sectorsPerCluster; // Cluster size in sectors. + uint8_t m_clusterSectorMask; // Mask to extract sector of cluster. + uint8_t m_sectorsPerClusterShift; // Cluster count to sector count shift. + uint8_t m_fatType = 0; // Volume type (12, 16, OR 32). + uint8_t m_fatCount; // FAT count (1 or 2). + uint16_t m_rootDirEntryCount; // Number of entries in FAT16 root dir. + Cluster_t m_allocSearchStart; // Start cluster for alloc search. + uint32_t m_sectorsPerFat; // FAT size in sectors + Sector_t m_dataStartSector; // First data sector number. + Sector_t m_fatStartSector; // Start sector for first FAT. + Cluster_t m_lastCluster; // Last cluster number in FAT. + Cluster_t m_rootDirStart; // Start sector FAT16, cluster FAT32. + //---------------------------------------------------------------------------- + // sector I/O functions. + bool cacheSafeRead(Sector_t sector, uint8_t* dst) { + return m_cache.cacheSafeRead(sector, dst); + } + bool cacheSafeRead(Sector_t sector, uint8_t* dst, size_t count) { + return m_cache.cacheSafeRead(sector, dst, count); + } + bool cacheSafeWrite(Sector_t sector, const uint8_t* dst) { + return m_cache.cacheSafeWrite(sector, dst); + } + bool cacheSafeWrite(Sector_t sector, const uint8_t* dst, size_t count) { + return m_cache.cacheSafeWrite(sector, dst, count); + } + bool syncDevice() { return m_blockDev->syncDevice(); } +#if MAINTAIN_FREE_CLUSTER_COUNT + int32_t m_freeClusterCount; // Count of free clusters in volume. + void setFreeClusterCount(int32_t value) { m_freeClusterCount = value; } + void updateFreeClusterCount(int32_t change) { + if (m_freeClusterCount >= 0) { + m_freeClusterCount += change; + } + } +#else // MAINTAIN_FREE_CLUSTER_COUNT + void setFreeClusterCount(int32_t value) { (void)value; } + void updateFreeClusterCount(int32_t change) { (void)change; } +#endif // MAINTAIN_FREE_CLUSTER_COUNT + // sector caches + FsCache m_cache; + FsCache* dataCache() { return &m_cache; } +#if USE_SEPARATE_FAT_CACHE + FsCache m_fatCache; + uint8_t* fatCachePrepare(Sector_t sector, uint8_t options) { + if (m_fatCount == 2) { + options |= FsCache::CACHE_STATUS_MIRROR_FAT; + } + return m_fatCache.prepare(sector, options); + } + bool cacheSync() { + return m_cache.sync() && m_fatCache.sync() && syncDevice(); + } +#else // USE_SEPARATE_FAT_CACHE + uint8_t* fatCachePrepare(Sector_t sector, uint8_t options) { + if (m_fatCount == 2) { + options |= FsCache::CACHE_STATUS_MIRROR_FAT; + } + return dataCachePrepare(sector, options); + } + bool cacheSync() { return m_cache.sync() && syncDevice(); } +#endif // USE_SEPARATE_FAT_CACHE + uint8_t* dataCachePrepare(Sector_t sector, uint8_t options) { + return m_cache.prepare(sector, options); + } + bool cacheSyncData() { return m_cache.sync(); } + uint8_t* cacheAddress() { return m_cache.cacheBuffer(); } + Sector_t cacheSectorNumber() { return m_cache.sector(); } + void cacheDirty() { m_cache.dirty(); } + //---------------------------------------------------------------------------- + bool allocateCluster(Cluster_t current, Cluster_t* next); + bool allocContiguous(uint32_t count, Cluster_t* firstCluster); + uint8_t sectorOfCluster(uint32_t position) const { + return (position >> 9) & m_clusterSectorMask; + } + Cluster_t clusterStartSector(Cluster_t cluster) const { + return m_dataStartSector + ((cluster - 2) << m_sectorsPerClusterShift); + } + int8_t fatGet(Cluster_t cluster, Cluster_t* value); + bool fatPut(Cluster_t cluster, Cluster_t value); + bool fatPutEOC(Cluster_t cluster) { return fatPut(cluster, 0x0FFFFFFF); } + bool freeChain(Cluster_t cluster); + bool isEOC(Cluster_t cluster) const { return cluster > m_lastCluster; } +}; diff --git a/third_party/sdfat/src/FatLib/FatVolume.cpp b/third_party/sdfat/src/FatLib/FatVolume.cpp new file mode 100644 index 00000000..f5c3633d --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatVolume.cpp @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatVolume.cpp" +#include "../common/DebugMacros.h" +#include "FatLib.h" +FatVolume* FatVolume::m_cwv = nullptr; +//------------------------------------------------------------------------------ +bool FatVolume::chdir(const char* path) { + FatFile dir; + if (!dir.open(vwd(), path, O_RDONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!dir.isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + m_vwd.copy(&dir); + return true; + +fail: + return false; +} diff --git a/third_party/sdfat/src/FatLib/FatVolume.h b/third_party/sdfat/src/FatLib/FatVolume.h new file mode 100644 index 00000000..591eb82a --- /dev/null +++ b/third_party/sdfat/src/FatLib/FatVolume.h @@ -0,0 +1,357 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "FatFile.h" +/** + * \file + * \brief FatVolume class + */ +//------------------------------------------------------------------------------ +/** + * \class FatVolume + * \brief Integration class for the FatLib library. + */ +class FatVolume : public FatPartition { + public: + /** Get file's user settable attributes. + * \param[in] path path to file. + * \return user settable file attributes for success else -1. + */ + int attrib(const char* path) { + File32 tmpFile; + return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib() : -1; + } + //---------------------------------------------------------------------------- + /** Set file's user settable attributes. + * \param[in] path path to file. + * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY, + * FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE. + * + * \return true for success or false for failure. + */ + bool attrib(const char* path, uint8_t bits) { + File32 tmpFile; + return tmpFile.open(this, path, O_RDONLY) ? tmpFile.attrib(bits) : false; + } + //---------------------------------------------------------------------------- + /** + * Initialize an FatVolume object. + * \param[in] dev Device block driver. + * \param[in] setCwv Set current working volume if true. + * \param[in] part partition to initialize. + * \param[in] startSector Start sector of volume if part is zero. + * \return true for success or false for failure. + */ + bool begin(FsBlockDevice* dev, bool setCwv = true, uint8_t part = 1, + Sector_t startSector = 0) { + if (!init(dev, part, startSector)) { + return false; + } + if (!chdir()) { + return false; + } + if (setCwv || !m_cwv) { + m_cwv = this; + } + return true; + } + //---------------------------------------------------------------------------- + /** Change global current working volume to this volume. */ + void chvol() { m_cwv = this; } + //---------------------------------------------------------------------------- + /** + * Set volume working directory to root. + * \return true for success or false for failure. + */ + bool chdir() { + m_vwd.close(); + return m_vwd.openRoot(this); + } + //---------------------------------------------------------------------------- + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const char* path); + //---------------------------------------------------------------------------- + /** + * Test for the existence of a file. + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + FatFile tmp; + return tmp.open(this, path, O_RDONLY); + } + //---------------------------------------------------------------------------- + /** List the directory contents of the volume root directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags = 0) { return m_vwd.ls(pr, flags); } + //---------------------------------------------------------------------------- + /** List the contents of a directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, const char* path, uint8_t flags) { + FatFile dir; + return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags); + } + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const char* path, bool pFlag = true) { + FatFile sub; + return sub.mkdir(vwd(), path, pFlag); + } + //---------------------------------------------------------------------------- + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a File32 object. + */ + File32 open(const char* path, oflag_t oflag = O_RDONLY) { + File32 tmpFile; + tmpFile.open(this, path, oflag); + return tmpFile; + } + //---------------------------------------------------------------------------- + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const char* path) { + FatFile tmp; + return tmp.open(this, path, O_WRONLY) && tmp.remove(); + } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const char* oldPath, const char* newPath) { + FatFile file; + return file.open(vwd(), oldPath, O_RDONLY) && file.rename(vwd(), newPath); + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const char* path) { + FatFile sub; + return sub.open(this, path, O_RDONLY) && sub.rmdir(); + } + //---------------------------------------------------------------------------- + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const char* path, uint32_t length) { + FatFile file; + return file.open(this, path, O_WRONLY) && file.truncate(length); + } +#if ENABLE_ARDUINO_SERIAL + //---------------------------------------------------------------------------- + /** List the directory contents of the root directory to Serial. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags = 0) { return ls(&Serial, flags); } + //---------------------------------------------------------------------------- + /** List the directory contents of a directory to Serial. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(const char* path, uint8_t flags = 0) { + return ls(&Serial, path, flags); + } +#endif // ENABLE_ARDUINO_SERIAL +#if ENABLE_ARDUINO_STRING + //---------------------------------------------------------------------------- + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const String& path) { return chdir(path.c_str()); } + //---------------------------------------------------------------------------- + /** + * Test for the existence of a file. + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const String& path) { return exists(path.c_str()); } + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const String& path, bool pFlag = true) { + return mkdir(path.c_str(), pFlag); + } + //---------------------------------------------------------------------------- + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a File32 object. + */ + File32 open(const String& path, oflag_t oflag = O_RDONLY) { + return open(path.c_str(), oflag); + } + //---------------------------------------------------------------------------- + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const String& path) { return remove(path.c_str()); } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const String& oldPath, const String& newPath) { + return rename(oldPath.c_str(), newPath.c_str()); + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const String& path) { return rmdir(path.c_str()); } + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const String& path, uint32_t length) { + return truncate(path.c_str(), length); + } +#endif // ENABLE_ARDUINO_STRING + + private: + friend FatFile; + static FatVolume* cwv() { return m_cwv; } + FatFile* vwd() { return &m_vwd; } + static FatVolume* m_cwv; + FatFile m_vwd; +}; diff --git a/third_party/sdfat/src/FreeStack.cpp b/third_party/sdfat/src/FreeStack.cpp new file mode 100644 index 00000000..d59840ac --- /dev/null +++ b/third_party/sdfat/src/FreeStack.cpp @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define FREE_STACK_CPP +#include "FreeStack.h" +#if defined(HAS_UNUSED_STACK) && HAS_UNUSED_STACK +//------------------------------------------------------------------------------ +inline char* stackBegin() { +#if defined(__AVR__) + return __brkval ? __brkval : &__bss_end; +#elif defined(__IMXRT1062__) + return reinterpret_cast(&_ebss); +#elif defined(__arm__) + return reinterpret_cast(sbrk(0)); +#else // defined(__AVR__) +#error "undefined stackBegin" +#endif // defined(__AVR__) +} +//------------------------------------------------------------------------------ +inline char* stackPointer() { +#if defined(__AVR__) + return reinterpret_cast(SP); +#elif defined(__arm__) + register uint32_t sp asm("sp"); + return reinterpret_cast(sp); +#else // defined(__AVR__) +#error "undefined stackPointer" +#endif // defined(__AVR__) +} +//------------------------------------------------------------------------------ +/** Stack fill pattern. */ +const char FILL = 0x55; +void FillStack() { + char* p = stackBegin(); + const char* top = stackPointer(); + while (p < top) { + *p++ = FILL; + } +} +//------------------------------------------------------------------------------ +// May fail if malloc or new is used. +int UnusedStack() { + char* h = stackBegin(); + const char* top = stackPointer(); + int n; + + for (n = 0; (h + n) < top; n++) { + if (h[n] != FILL) { + if (n >= 16) { + break; + } + // Attempt to skip used heap. + h += n; + n = 0; + } + } + return n; +} +#endif // defined(HAS_UNUSED_STACK) && HAS_UNUSED_STACK diff --git a/third_party/sdfat/src/FreeStack.h b/third_party/sdfat/src/FreeStack.h new file mode 100644 index 00000000..aeecc926 --- /dev/null +++ b/third_party/sdfat/src/FreeStack.h @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief FreeStack() function. + */ +#include +#if defined(__AVR__) || defined(DOXYGEN) +#include +/** Indicate FillStack() and UnusedStack() are available. */ +#define HAS_UNUSED_STACK 1 +/** boundary between stack and heap. */ +extern char* __brkval; +/** End of bss section.*/ +extern char __bss_end; +/** Amount of free stack space. + * \return The number of free bytes. + */ +inline int FreeStack() { + const char* sp = reinterpret_cast(SP); + return __brkval ? sp - __brkval : sp - &__bss_end; +} +#elif defined(__IMXRT1062__) +#define HAS_UNUSED_STACK 1 +extern uint8_t _ebss; +inline int FreeStack() { + register uint32_t sp asm("sp"); + return reinterpret_cast(sp) - reinterpret_cast(&_ebss); +} +#elif defined(__arm__) +#define HAS_UNUSED_STACK 1 +extern "C" char* sbrk(int incr); +inline int FreeStack() { + register uint32_t sp asm("sp"); + return reinterpret_cast(sp) - reinterpret_cast(sbrk(0)); +} +#else // defined(__AVR__) || defined(DOXYGEN) +#ifndef FREE_STACK_CPP +#warning FreeStack is not defined for this system. +#endif // FREE_STACK_CPP +inline int FreeStack() { return 0; } +#endif // defined(__AVR__) || defined(DOXYGEN) +#if defined(HAS_UNUSED_STACK) || defined(DOXYGEN) +/** Fill stack with 0x55 pattern */ +void FillStack(); +/** + * Determine the amount of unused stack. + * + * FillStack() must be called to fill the stack with a 0x55 pattern. + * + * UnusedStack() may fail if malloc() or new is use. + * + * \return number of bytes with 0x55 pattern. + */ +int UnusedStack(); +#else // HAS_UNUSED_STACK +#define HAS_UNUSED_STACK 0 +inline void FillStack() {} +inline int UnusedStack() { return 0; } +#endif // defined(HAS_UNUSED_STACK) diff --git a/third_party/sdfat/src/FsLib/FsFile.cpp b/third_party/sdfat/src/FsLib/FsFile.cpp new file mode 100644 index 00000000..38a27050 --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsFile.cpp @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsLib.h" +#if FILE_COPY_CONSTRUCTOR_SELECT +//------------------------------------------------------------------------------ +FsBaseFile::FsBaseFile(const FsBaseFile& from) { copy(&from); } +//------------------------------------------------------------------------------ +FsBaseFile& FsBaseFile::operator=(const FsBaseFile& from) { + copy(&from); + return *this; +} +#endif // FILE_COPY_CONSTRUCTOR_SELECT +//------------------------------------------------------------------------------ +void FsBaseFile::copy(const FsBaseFile* from) { + if (from != this) { + m_fFile = nullptr; + m_xFile = nullptr; + if (from->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + m_fFile->copy(from->m_fFile); + } else if (from->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + m_xFile->copy(from->m_xFile); + } + } +} +//------------------------------------------------------------------------------ +void FsBaseFile::move(FsBaseFile* from) { + if (from != this) { + copy(from); + from->m_fFile = nullptr; + from->m_xFile = nullptr; + } +} +//------------------------------------------------------------------------------ +bool FsBaseFile::close() { + bool rtn = m_fFile ? m_fFile->close() : m_xFile ? m_xFile->close() : true; + m_fFile = nullptr; + m_xFile = nullptr; + return rtn; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::mkdir(FsBaseFile* dir, const char* path, bool pFlag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->mkdir(dir->m_fFile, path, pFlag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->mkdir(dir->m_xFile, path, pFlag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::open(FsVolume* vol, const char* path, oflag_t oflag) { + if (!vol) { + return false; + } + close(); + if (vol->m_fVol) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile && m_fFile->open(vol->m_fVol, path, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (vol->m_xVol) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile && m_xFile->open(vol->m_xVol, path, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::open(FsBaseFile* dir, const char* path, oflag_t oflag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->open(dir->m_fFile, path, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->open(dir->m_xFile, path, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::open(FsBaseFile* dir, uint32_t index, oflag_t oflag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->open(dir->m_fFile, index, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->open(dir->m_xFile, index, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::openCwd() { + close(); + if (FsVolume::m_cwv && FsVolume::m_cwv->m_fVol) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->openCwd()) { + return true; + } + m_fFile = nullptr; + } else if (FsVolume::m_cwv && FsVolume::m_cwv->m_xVol) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->openCwd()) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::openNext(FsBaseFile* dir, oflag_t oflag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->openNext(dir->m_fFile, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->openNext(dir->m_xFile, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::openRoot(FsVolume* vol) { + if (!vol) { + return false; + } + close(); + if (vol->m_fVol) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile && m_fFile->openRoot(vol->m_fVol)) { + return true; + } + m_fFile = nullptr; + } else if (vol->m_xVol) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile && m_xFile->openRoot(vol->m_xVol)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::remove() { + if (m_fFile) { + if (m_fFile->remove()) { + m_fFile = nullptr; + return true; + } + } else if (m_xFile) { + if (m_xFile->remove()) { + m_xFile = nullptr; + return true; + } + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::rmdir() { + if (m_fFile) { + if (m_fFile->rmdir()) { + m_fFile = nullptr; + return true; + } + } else if (m_xFile) { + if (m_xFile->rmdir()) { + m_xFile = nullptr; + return true; + } + } + return false; +} diff --git a/third_party/sdfat/src/FsLib/FsFile.h b/third_party/sdfat/src/FsLib/FsFile.h new file mode 100644 index 00000000..2f80bc12 --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsFile.h @@ -0,0 +1,934 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief FsBaseFile include file. + */ +#include "ExFatLib/ExFatLib.h" +#include "FatLib/FatLib.h" +#include "FsNew.h" +#include "FsVolume.h" +/** + * \class FsBaseFile + * \brief FsBaseFile class. + */ +class FsBaseFile { + public: + /** Create an instance. */ + FsBaseFile() = default; // cppcheck-suppress uninitMemberVar + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). + */ + // cppcheck-suppress uninitMemberVar + FsBaseFile(const char* path, oflag_t oflag) { open(path, oflag); } + + /** Copy from to this. + * \param[in] from Source file. + */ + void copy(const FsBaseFile* from); + + /** move from to this. + * \param[in] from Source file. + */ + void move(FsBaseFile* from); + +#if FILE_COPY_CONSTRUCTOR_SELECT == FILE_COPY_CONSTRUCTOR_PUBLIC + /** Copy constructor. + * \param[in] from Object used to initialize this instance. + */ + FsBaseFile(const FsBaseFile& from); + /** Copy assignment operator + * \param[in] from Object used to initialize this instance. + * \return assigned object. + */ + FsBaseFile& operator=(const FsBaseFile& from); +#elif FILE_COPY_CONSTRUCTOR_SELECT == FILE_COPY_CONSTRUCTOR_PRIVATE + + private: + FsBaseFile(const FsBaseFile& from); + FsBaseFile& operator=(const FsBaseFile& from); + + public: +#else // FILE_COPY_CONSTRUCTOR_SELECT + FsBaseFile(const FsBaseFile& from) = delete; + FsBaseFile& operator=(const FsBaseFile& from) = delete; +#endif // FILE_COPY_CONSTRUCTOR_SELECT + +#if FILE_MOVE_CONSTRUCTOR_SELECT + /** Move constructor. + * \param[in] from File to move. + */ + FsBaseFile(FsBaseFile&& from) { move(&from); } + /** Move assignment operator. + * \param[in] from File to move. + * \return Assigned file. + */ + FsBaseFile& operator=(FsBaseFile&& from) { + move(&from); + return *this; + } +#else // FILE_MOVE_CONSTRUCTOR_SELECT + FsBaseFile(FsBaseFile&& from) = delete; + FsBaseFile& operator=(FsBaseFile&& from) = delete; +#endif // FILE_MOVE_CONSTRUCTOR_SELECT + +#if DESTRUCTOR_CLOSES_FILE + ~FsBaseFile() { + if (isOpen()) { + close(); + } + } +#else // DESTRUCTOR_CLOSES_FILE + ~FsBaseFile() = default; +#endif // DESTRUCTOR_CLOSES_FILE + + /** The parenthesis operator. + * + * \return true if a file is open. + */ + operator bool() const { return isOpen(); } + /** + * \return user settable file attributes for success else -1. + */ + int attrib() { + return m_fFile ? m_fFile->attrib() : m_xFile ? m_xFile->attrib() : -1; + } + /** Set file attributes + * + * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY, + * FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE. + * + * \note attrib() will fail for set read-only if the file is open for write. + * \return true for success or false for failure. + */ + bool attrib(uint8_t bits) { + return m_fFile ? m_fFile->attrib(bits) + : m_xFile ? m_xFile->attrib(bits) + : false; + } + /** \return number of bytes available from the current position to EOF + * or INT_MAX if more than INT_MAX bytes are available. + */ + int available() const { + return m_fFile ? m_fFile->available() : m_xFile ? m_xFile->available() : 0; + } + /** \return The number of bytes available from the current position + * to EOF for normal files. Zero is returned for directory files. + */ + uint64_t available64() const { + return m_fFile ? m_fFile->available32() + : m_xFile ? m_xFile->available64() + : 0; + } + /** Clear writeError. */ + void clearWriteError() { + if (m_fFile) m_fFile->clearWriteError(); + if (m_xFile) m_xFile->clearWriteError(); + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool close(); + /** Check for contiguous file and return its raw sector range. + * + * \param[out] bgnSector the first sector address for the file. + * \param[out] endSector the last sector address for the file. + * + * Set contiguous flag for FAT16/FAT32 files. + * Parameters may be nullptr. + * + * \return true for success or false for failure. + */ + bool contiguousRange(Sector_t* bgnSector, Sector_t* endSector) { + return m_fFile ? m_fFile->contiguousRange(bgnSector, endSector) + : m_xFile ? m_xFile->contiguousRange(bgnSector, endSector) + : false; + } + /** \return The current cluster number for a file or directory. */ + Cluster_t curCluster() const { + return m_fFile ? m_fFile->curCluster() + : m_xFile ? m_xFile->curCluster() + : 0; + } + /** \return The current position for a file or directory. */ + uint64_t curPosition() const { + return m_fFile ? m_fFile->curPosition() + : m_xFile ? m_xFile->curPosition() + : 0; + } + /** \return Total allocated length for file. */ + uint64_t dataLength() const { + return m_fFile ? m_fFile->fileSize() : m_xFile ? m_xFile->dataLength() : 0; + } + /** \return Directory entry index. */ + uint32_t dirIndex() const { + return m_fFile ? m_fFile->dirIndex() : m_xFile ? m_xFile->dirIndex() : 0; + } + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * The calling instance must be an open directory file. + * + * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory + * dirFile. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + return m_fFile ? m_fFile->exists(path) + : m_xFile ? m_xFile->exists(path) + : false; + } + /** get position for streams + * \param[out] pos struct to receive position + */ + void fgetpos(fspos_t* pos) const { + if (m_fFile) m_fFile->fgetpos(pos); + if (m_xFile) m_xFile->fgetpos(pos); + } + /** + * Get a string from a file. + * + * fgets() reads bytes from a file into the array pointed to by \a str, until + * \a num - 1 bytes are read, or a delimiter is read and transferred to \a + * str, or end-of-file is encountered. The string is then terminated with a + * null byte. + * + * fgets() deletes CR, '\\r', from the string. This insures only a '\\n' + * terminates the string for Windows text files which use CRLF for newline. + * + * \param[out] str Pointer to the array where the string is stored. + * \param[in] num Maximum number of characters to be read + * (including the final null byte). Usually the length + * of the array \a str is used. + * \param[in] delim Optional set of delimiters. The default is "\n". + * + * \return For success fgets() returns the length of the string in \a str. + * If no data is read, fgets() returns zero for EOF or -1 if an error + * occurred. + */ + int fgets(char* str, int num, char* delim = nullptr) { + return m_fFile ? m_fFile->fgets(str, num, delim) + : m_xFile ? m_xFile->fgets(str, num, delim) + : -1; + } + /** \return The total number of bytes in a file. */ + uint64_t fileSize() const { + return m_fFile ? m_fFile->fileSize() : m_xFile ? m_xFile->fileSize() : 0; + } + /** \return The first cluster number for a file or directory. */ + Cluster_t firstCluster() const { + return m_fFile ? m_fFile->firstCluster() + : m_xFile ? m_xFile->firstCluster() + : 0; + } + /** \return Address of first sector or zero for empty file. */ + Sector_t firstSector() const { + return m_fFile ? m_fFile->firstSector() + : m_xFile ? m_xFile->firstSector() + : 0; + } + /** Ensure that any bytes written to the file are saved to the SD card. */ + void flush() { sync(); } + /** set position for streams + * \param[in] pos struct with value for new position + */ + void fsetpos(const fspos_t* pos) { + if (m_fFile) m_fFile->fsetpos(pos); + if (m_xFile) m_xFile->fsetpos(pos); + } + /** Get a file's access date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { + return m_fFile ? m_fFile->getAccessDateTime(pdate, ptime) + : m_xFile ? m_xFile->getAccessDateTime(pdate, ptime) + : false; + } + /** Get a file's create date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { + return m_fFile ? m_fFile->getCreateDateTime(pdate, ptime) + : m_xFile ? m_xFile->getCreateDateTime(pdate, ptime) + : false; + } + /** \return All error bits. */ + uint8_t getError() const { + return m_fFile ? m_fFile->getError() : m_xFile ? m_xFile->getError() : 0XFF; + } + /** Get a file's Modify date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime) { + return m_fFile ? m_fFile->getModifyDateTime(pdate, ptime) + : m_xFile ? m_xFile->getModifyDateTime(pdate, ptime) + : false; + } + /** + * Get a file's name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * \param[in] len The size of the array in bytes. The array + * must be at least 13 bytes long. The file's name will be + * truncated if the file's name is too long. + * \return The length of the returned string. + */ + size_t getName(char* name, size_t len) { + *name = 0; + return m_fFile ? m_fFile->getName(name, len) + : m_xFile ? m_xFile->getName(name, len) + : 0; + } + + /** \return value of writeError */ + bool getWriteError() const { + return m_fFile ? m_fFile->getWriteError() + : m_xFile ? m_xFile->getWriteError() + : true; + } + /** + * Check for FsBlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy() { + return m_fFile ? m_fFile->isBusy() : m_xFile ? m_xFile->isBusy() : true; + } + /** \return True if the file is contiguous. */ + bool isContiguous() const { +#if USE_FAT_FILE_FLAG_CONTIGUOUS + return m_fFile ? m_fFile->isContiguous() + : m_xFile ? m_xFile->isContiguous() + : false; +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + return m_xFile ? m_xFile->isContiguous() : false; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + } + /** \return True if this is a directory else false. */ + bool isDir() const { + return m_fFile ? m_fFile->isDir() : m_xFile ? m_xFile->isDir() : false; + } + /** \return True if this is a normal file. */ + bool isFile() const { + return m_fFile ? m_fFile->isFile() : m_xFile ? m_xFile->isFile() : false; + } + /** \return True if this is a normal file or sub-directory. */ + bool isFileOrSubDir() const { + return m_fFile ? m_fFile->isFileOrSubDir() + : m_xFile ? m_xFile->isFileOrSubDir() + : false; + } + /** \return True if this is a hidden file else false. */ + bool isHidden() const { + return m_fFile ? m_fFile->isHidden() + : m_xFile ? m_xFile->isHidden() + : false; + } + /** \return True if this is an open file/directory else false. */ + bool isOpen() const { return m_fFile || m_xFile; } + /** \return True file is readable. */ + bool isReadable() const { + return m_fFile ? m_fFile->isReadable() + : m_xFile ? m_xFile->isReadable() + : false; + } + /** \return True if file is read-only */ + bool isReadOnly() const { + return m_fFile ? m_fFile->isReadOnly() + : m_xFile ? m_xFile->isReadOnly() + : false; + } + /** \return True if this is a sub-directory file else false. */ + bool isSubDir() const { + return m_fFile ? m_fFile->isSubDir() + : m_xFile ? m_xFile->isSubDir() + : false; + } + /** \return True if this is a System file else false. */ + bool isSystem() const { + return m_fFile ? m_fFile->isSystem() + : m_xFile ? m_xFile->isSystem() + : false; + } + /** \return True file is writable. */ + bool isWritable() const { + return m_fFile ? m_fFile->isWritable() + : m_xFile ? m_xFile->isWritable() + : false; + } +#if ENABLE_ARDUINO_SERIAL + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * \return true for success or false for failure. + */ + bool ls(uint8_t flags) { return ls(&Serial, flags); } + /** List directory contents. + * \return true for success or false for failure. + */ + bool ls() { return ls(&Serial); } +#endif // ENABLE_ARDUINO_SERIAL + /** List directory contents. + * + * \param[in] pr Print object. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr) { + return m_fFile ? m_fFile->ls(pr) : m_xFile ? m_xFile->ls(pr) : false; + } + /** List directory contents. + * + * \param[in] pr Print object. + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags) { + return m_fFile ? m_fFile->ls(pr, flags) + : m_xFile ? m_xFile->ls(pr, flags) + : false; + } + /** Make a new directory. + * + * \param[in] dir An open FatFile instance for the directory that will + * contain the new directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the new directory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(FsBaseFile* dir, const char* path, bool pFlag = true); + /** Open a file or directory by name. + * + * \param[in] dir An open file instance for the directory containing + * the file to be opened. + * + * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of flags from the following list + * + * O_RDONLY - Open for reading only.. + * + * O_READ - Same as O_RDONLY. + * + * O_WRONLY - Open for writing only. + * + * O_WRITE - Same as O_WRONLY. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_AT_END - Set the initial position at the end of the file. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file + * exists. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated to + * 0. + * + * WARNING: A given file must not be opened by more than one file object + * or file corruption may occur. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return true for success or false for failure. + */ + bool open(FsBaseFile* dir, const char* path, oflag_t oflag = O_RDONLY); + /** Open a file by index. + * + * \param[in] dir An open FsFile instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FsFile::open(FsFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(FsBaseFile* dir, uint32_t index, oflag_t oflag = O_RDONLY); + /** Open a file or directory by name. + * + * \param[in] vol Volume where the file is located. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. + * + * \return true for success or false for failure. + */ + bool open(FsVolume* vol, const char* path, oflag_t oflag = O_RDONLY); + /** Open a file or directory by name. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. + * + * \return true for success or false for failure. + */ + bool open(const char* path, oflag_t oflag = O_RDONLY) { + return FsVolume::m_cwv && open(FsVolume::m_cwv, path, oflag); + } + /** Open a file or directory by index in the current working directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. + * + * \return true for success or false for failure. + */ + bool open(uint32_t index, oflag_t oflag = O_RDONLY) { + FsBaseFile cwd; + return cwd.openCwd() && open(&cwd, index, oflag); + } + /** Open the current working directory. + * + * \return true for success or false for failure. + */ + bool openCwd(); + /** Opens the next file or folder in a directory. + * \param[in] dir directory containing files. + * \param[in] oflag open flags. + * \return a file object. + */ + bool openNext(FsBaseFile* dir, oflag_t oflag = O_RDONLY); + /** Open a volume's root directory. + * + * \param[in] vol The SdFs volume containing the root directory to be opened. + * + * \return true for success or false for failure. + */ + bool openRoot(FsVolume* vol); + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek() { + return m_fFile ? m_fFile->peek() : m_xFile ? m_xFile->peek() : -1; + } + /** Allocate contiguous clusters to an empty file. + * + * The file must be empty with no clusters allocated. + * + * The file will contain uninitialized data for FAT16/FAT32 files. + * exFAT files will have zero validLength and dataLength will equal + * the requested length. + * + * \param[in] length size of the file in bytes. + * \return true for success or false for failure. + */ + bool preAllocate(uint64_t length) { + return m_fFile ? length < (1ULL << 32) && m_fFile->preAllocate(length) + : m_xFile ? m_xFile->preAllocate(length) + : false; + } + /** Print a file's access date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printAccessDateTime(print_t* pr) { + return m_fFile ? m_fFile->printAccessDateTime(pr) + : m_xFile ? m_xFile->printAccessDateTime(pr) + : 0; + } + /** Print a file's creation date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printCreateDateTime(print_t* pr) { + return m_fFile ? m_fFile->printCreateDateTime(pr) + : m_xFile ? m_xFile->printCreateDateTime(pr) + : 0; + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + return m_fFile ? m_fFile->printField(value, term, prec) + : m_xFile ? m_xFile->printField(value, term, prec) + : 0; + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + return m_fFile ? m_fFile->printField(value, term) + : m_xFile ? m_xFile->printField(value, term) + : 0; + } + /** Print a file's size. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printFileSize(print_t* pr) { + return m_fFile ? m_fFile->printFileSize(pr) + : m_xFile ? m_xFile->printFileSize(pr) + : 0; + } + /** Print a file's modify date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printModifyDateTime(print_t* pr) { + return m_fFile ? m_fFile->printModifyDateTime(pr) + : m_xFile ? m_xFile->printModifyDateTime(pr) + : 0; + } + /** Print a file's name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName(print_t* pr) { + return m_fFile ? m_fFile->printName(pr) + : m_xFile ? m_xFile->printName(pr) + : 0; + } + /** Read the next byte from a file. + * + * \return For success return the next byte in the file as an int. + * If an error occurs or end of file is reached return -1. + */ + int read() { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + /** Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] count Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a count, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. Possible errors include + * read() called before a file has been opened, corrupt file system + * or an I/O error occurred. + */ + int read(void* buf, size_t count) { + return m_fFile ? m_fFile->read(buf, count) + : m_xFile ? m_xFile->read(buf, count) + : -1; + } + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] path Path for the file to be removed. + * + * Example use: dirFile.remove(filenameToRemove); + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(const char* path) { + return m_fFile ? m_fFile->remove(path) + : m_xFile ? m_xFile->remove(path) + : false; + } + /** Rename a file or subdirectory. + * + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(const char* newPath) { + return m_fFile ? m_fFile->rename(newPath) + : m_xFile ? m_xFile->rename(newPath) + : false; + } + /** Rename a file or subdirectory. + * + * \param[in] dir Directory for the new path. + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(FsBaseFile* dir, const char* newPath) { + return m_fFile && dir->m_fFile ? m_fFile->rename(dir->m_fFile, newPath) + : m_xFile && dir->m_xFile ? m_xFile->rename(dir->m_xFile, newPath) + : false; + } + /** Set the file's current position to zero. */ + void rewind() { + if (m_fFile) m_fFile->rewind(); + if (m_xFile) m_xFile->rewind(); + } + /** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmdir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return true for success or false for failure. + */ + bool rmdir(); + /** Set the files position to current position + \a pos. See seekSet(). + * \param[in] offset The new position in bytes from the current position. + * \return true for success or false for failure. + */ + bool seekCur(int64_t offset) { return seekSet(curPosition() + offset); } + /** Set the files position to end-of-file + \a offset. See seekSet(). + * Can't be used for directory files since file size is not defined. + * \param[in] offset The new position in bytes from end-of-file. + * \return true for success or false for failure. + */ + bool seekEnd(int64_t offset = 0) { return seekSet(fileSize() + offset); } + /** Sets a file's position. + * + * \param[in] pos The new position in bytes from the beginning of the file. + * + * \return true for success or false for failure. + */ + bool seekSet(uint64_t pos) { + return m_fFile ? pos < (1ULL << 32) && + m_fFile->seekSet(static_cast(pos)) + : m_xFile ? m_xFile->seekSet(pos) + : false; + } + /** The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool sync() { + return m_fFile ? m_fFile->sync() : m_xFile ? m_xFile->sync() : false; + } + /** Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive + * OR of flags from the following list + * + * T_ACCESS - Set the file's last access date and time. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2099 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return true for success or false for failure. + */ + bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second) { + return m_fFile ? m_fFile->timestamp(flags, year, month, day, hour, minute, + second) + : m_xFile ? m_xFile->timestamp(flags, year, month, day, hour, minute, + second) + : false; + } + /** Truncate a file to the current position. + * + * \return true for success or false for failure. + */ + bool truncate() { + return m_fFile ? m_fFile->truncate() + : m_xFile ? m_xFile->truncate() + : false; + } + /** Truncate a file to a specified length. + * The current file position will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(uint64_t length) { + return m_fFile ? length < (1ULL << 32) && m_fFile->truncate(length) + : m_xFile ? m_xFile->truncate(length) + : false; + } + /** \return The valid number of bytes in a file. */ + uint64_t validLength() const { + return m_fFile ? m_fFile->fileSize() : m_xFile ? m_xFile->validLength() : 0; + } + /** Write a string to a file. Used by the Arduino Print class. + * \param[in] str Pointer to the string. + * Use getWriteError to check for errors. + * \return count of characters written for success or -1 for failure. + */ + size_t write(const char* str) { return write(str, strlen(str)); } + /** Write a byte to a file. Required by the Arduino Print class. + * \param[in] b the byte to be written. + * Use getWriteError to check for errors. + * \return 1 for success and 0 for failure. + */ + size_t write(uint8_t b) { return write(&b, 1); } + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] count Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a nbyte. If an error occurs, write() returns zero and writeError is set. + */ + size_t write(const void* buf, size_t count) { + return m_fFile ? m_fFile->write(buf, count) + : m_xFile ? m_xFile->write(buf, count) + : 0; + } + + private: + newalign_t m_fileMem[FS_ALIGN_DIM(ExFatFile, FatFile)]; + FatFile* m_fFile = nullptr; + ExFatFile* m_xFile = nullptr; +}; +/** + * \class FsFile + * \brief FsBaseFile file with Arduino Stream. + */ +class FsFile : public StreamFile { + public: + FsFile() {} + /** Create an open FsFile. + * \param[in] path path for file. + * \param[in] oflag open flags. + */ + FsFile(const char* path, oflag_t oflag) { open(path, oflag); } + /** Opens the next file or folder in a directory. + * + * \param[in] oflag open flags. + * \return a FatStream object. + */ + FsFile openNextFile(oflag_t oflag = O_RDONLY) { + FsFile tmpFile; + tmpFile.openNext(this, oflag); + return tmpFile; + } +}; diff --git a/third_party/sdfat/src/FsLib/FsFormatter.h b/third_party/sdfat/src/FsLib/FsFormatter.h new file mode 100644 index 00000000..cb8880f6 --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsFormatter.h @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "ExFatLib/ExFatLib.h" +#include "FatLib/FatLib.h" +/** + * \class FsFormatter + * \brief Format a exFAT/FAT volume. + */ +class FsFormatter { + public: + /** Constructor. */ + FsFormatter() = default; + /** + * Format a FAT volume. + * + * \param[in] dev Block device for volume. + * \param[in] secBuffer buffer for writing to volume. + * \param[in] pr Print device for progress output. + * + * \return true for success or false for failure. + */ + bool format(FsBlockDevice* dev, uint8_t* secBuffer, print_t* pr = nullptr) { + Sector_t sectorCount = dev->sectorCount(); + if (sectorCount == 0) { + return false; + } + return sectorCount <= 67108864 ? m_fFmt.format(dev, secBuffer, pr) + : m_xFmt.format(dev, secBuffer, pr); + } + + private: + FatFormatter m_fFmt; + ExFatFormatter m_xFmt; +}; diff --git a/third_party/sdfat/src/FsLib/FsLib.h b/third_party/sdfat/src/FsLib/FsLib.h new file mode 100644 index 00000000..b94ba50a --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsLib.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief FsLib include file. + */ +#pragma once +#include "FsFile.h" +#include "FsFormatter.h" +#include "FsVolume.h" diff --git a/third_party/sdfat/src/FsLib/FsNew.cpp b/third_party/sdfat/src/FsLib/FsNew.cpp new file mode 100644 index 00000000..db0ee709 --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsNew.cpp @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsNew.h" +void* operator new(size_t size, newalign_t* ptr) { + (void)size; + return ptr; +} diff --git a/third_party/sdfat/src/FsLib/FsNew.h b/third_party/sdfat/src/FsLib/FsNew.h new file mode 100644 index 00000000..a9b7df9a --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsNew.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include +#include + +/** 32-bit alignment */ +typedef uint32_t newalign_t; + +/** Size required for exFAT or FAT class. */ +#define FS_SIZE(etype, ftype) \ + (sizeof(ftype) < sizeof(etype) ? sizeof(etype) : sizeof(ftype)) + +/** Dimension of aligned area. */ +#define NEW_ALIGN_DIM(n) \ + ((static_cast(n) + sizeof(newalign_t) - 1U) / sizeof(newalign_t)) + +/** Dimension of aligned area for etype or ftype class. */ +#define FS_ALIGN_DIM(etype, ftype) NEW_ALIGN_DIM(FS_SIZE(etype, ftype)) + +/** Custom new placement operator */ +void* operator new(size_t size, newalign_t* ptr); diff --git a/third_party/sdfat/src/FsLib/FsVolume.cpp b/third_party/sdfat/src/FsLib/FsVolume.cpp new file mode 100644 index 00000000..6f5bbbb3 --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsVolume.cpp @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsLib.h" +FsVolume* FsVolume::m_cwv = nullptr; +//------------------------------------------------------------------------------ +bool FsVolume::begin(FsBlockDevice* blockDev, bool setCwv, uint8_t part, + Sector_t startSector) { + m_fVol = nullptr; + m_xVol = new (m_volMem) ExFatVolume; + if (m_xVol && m_xVol->begin(blockDev, false, part, startSector)) { + goto done; + } + m_xVol = nullptr; + m_fVol = new (m_volMem) FatVolume; + if (m_fVol && m_fVol->begin(blockDev, false, part, startSector)) { + goto done; + } + m_fVol = nullptr; + return false; + +done: + if (setCwv || !m_cwv) { + m_cwv = this; + } + return true; +} +//------------------------------------------------------------------------------ +bool FsVolume::ls(print_t* pr, const char* path, uint8_t flags) { + FsBaseFile dir; + return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags); +} +//------------------------------------------------------------------------------ +FsFile FsVolume::open(const char* path, oflag_t oflag) { + FsFile tmpFile; + tmpFile.open(this, path, oflag); + return tmpFile; +} +#if ENABLE_ARDUINO_STRING +//------------------------------------------------------------------------------ +FsFile FsVolume::open(const String& path, oflag_t oflag) { + return open(path.c_str(), oflag); +} +#endif // ENABLE_ARDUINO_STRING diff --git a/third_party/sdfat/src/FsLib/FsVolume.h b/third_party/sdfat/src/FsLib/FsVolume.h new file mode 100644 index 00000000..8f0f30da --- /dev/null +++ b/third_party/sdfat/src/FsLib/FsVolume.h @@ -0,0 +1,436 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief FsVolume include file. + */ +#include "../ExFatLib/ExFatLib.h" +#include "../FatLib/FatLib.h" +#include "FsNew.h" + +class FsFile; +/** + * \class FsVolume + * \brief FsVolume class. + */ +class FsVolume { + public: + FsVolume() = default; // cppcheck-suppress uninitMemberVar + + ~FsVolume() { end(); } + /** Get file's user settable attributes. + * \param[in] path path to file. + * \return user settable file attributes for success else -1. + */ + //---------------------------------------------------------------------------- + int attrib(const char* path) { + return m_fVol ? m_fVol->attrib(path) : m_xVol ? m_xVol->attrib(path) : -1; + } + //---------------------------------------------------------------------------- + /** Set file's user settable attributes. + * \param[in] path path to file. + * \param[in] bits bit-wise or of selected attributes: FS_ATTRIB_READ_ONLY, + * FS_ATTRIB_HIDDEN, FS_ATTRIB_SYSTEM, FS_ATTRIB_ARCHIVE. + * + * \return true for success or false for failure. + */ + bool attrib(const char* path, uint8_t bits) { + return m_fVol ? m_fVol->attrib(path, bits) + : m_xVol ? m_xVol->attrib(path, bits) + : false; + } + //---------------------------------------------------------------------------- + /** + * Initialize an FatVolume object. + * \param[in] blockDev Device block driver. + * \param[in] setCwv Set current working volume if true. + * \param[in] part partition to initialize. + * \param[in] startSector Start sector of volume if part is zero. + * \return true for success or false for failure. + */ + bool begin(FsBlockDevice* blockDev, bool setCwv = true, uint8_t part = 1, + Sector_t startSector = 0); + //---------------------------------------------------------------------------- + /** \return the number of bytes in a cluster. */ + uint32_t bytesPerCluster() const { + return m_fVol ? m_fVol->bytesPerCluster() + : m_xVol ? m_xVol->bytesPerCluster() + : 0; + } + //---------------------------------------------------------------------------- + /** + * Set volume working directory to root. + * \return true for success or false for failure. + */ + bool chdir() { + return m_fVol ? m_fVol->chdir() : m_xVol ? m_xVol->chdir() : false; + } + //---------------------------------------------------------------------------- + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const char* path) { + return m_fVol ? m_fVol->chdir(path) : m_xVol ? m_xVol->chdir(path) : false; + } + //---------------------------------------------------------------------------- + /** Change global working volume to this volume. */ + void chvol() { m_cwv = this; } + /** \return The total number of clusters in the volume. */ + Cluster_t clusterCount() const { + return m_fVol ? m_fVol->clusterCount() + : m_xVol ? m_xVol->clusterCount() + : 0; + } + //---------------------------------------------------------------------------- + /** \return The logical sector number for the start of file data. */ + Sector_t dataStartSector() const { + return m_fVol ? m_fVol->dataStartSector() + : m_xVol ? m_xVol->clusterHeapStartSector() + : 0; + } + //---------------------------------------------------------------------------- + /** End access to volume + * \return pointer to sector size buffer for format. + */ + uint8_t* end() { + m_fVol = nullptr; + m_xVol = nullptr; + static_assert(sizeof(m_volMem) >= 512, "m_volMem too small"); + return reinterpret_cast(m_volMem); + } + //---------------------------------------------------------------------------- + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + return m_fVol ? m_fVol->exists(path) + : m_xVol ? m_xVol->exists(path) + : false; + } + //---------------------------------------------------------------------------- + /** \return The number of File Allocation Tables. */ + uint8_t fatCount() const { + return m_fVol ? m_fVol->fatCount() : m_xVol ? m_xVol->fatCount() : 0; + } + //---------------------------------------------------------------------------- + /** \return The logical sector number for the start of the first FAT. */ + Sector_t fatStartSector() const { + return m_fVol ? m_fVol->fatStartSector() + : m_xVol ? m_xVol->fatStartSector() + : 0; + } + //---------------------------------------------------------------------------- + /** \return Partition type, FAT_TYPE_EXFAT, FAT_TYPE_FAT32, + * FAT_TYPE_FAT16, or zero for error. + */ + uint8_t fatType() const { + return m_fVol ? m_fVol->fatType() : m_xVol ? m_xVol->fatType() : 0; + } + //---------------------------------------------------------------------------- + /** \return free cluster count or -1 if an error occurs. */ + int32_t freeClusterCount() const { + return m_fVol ? m_fVol->freeClusterCount() + : m_xVol ? m_xVol->freeClusterCount() + : -1; + } + //---------------------------------------------------------------------------- + /** + * Check for device busy. + * + * \return true if busy else false. + */ + bool isBusy() { + return m_fVol ? m_fVol->isBusy() : m_xVol ? m_xVol->isBusy() : false; + } + //---------------------------------------------------------------------------- + /** List directory contents. + * + * \param[in] pr Print object. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr) { + return m_fVol ? m_fVol->ls(pr) : m_xVol ? m_xVol->ls(pr) : false; + } + //---------------------------------------------------------------------------- + /** List directory contents. + * + * \param[in] pr Print object. + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags) { + return m_fVol ? m_fVol->ls(pr, flags) + : m_xVol ? m_xVol->ls(pr, flags) + : false; + } + //---------------------------------------------------------------------------- + /** List the directory contents of a directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, const char* path, uint8_t flags); + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const char* path, bool pFlag = true) { + return m_fVol ? m_fVol->mkdir(path, pFlag) + : m_xVol ? m_xVol->mkdir(path, pFlag) + : false; + } + //---------------------------------------------------------------------------- + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a FsBaseFile object. + */ + FsFile open(const char* path, oflag_t oflag = O_RDONLY); + //---------------------------------------------------------------------------- + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const char* path) { + return m_fVol ? m_fVol->remove(path) + : m_xVol ? m_xVol->remove(path) + : false; + } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const char* oldPath, const char* newPath) { + return m_fVol ? m_fVol->rename(oldPath, newPath) + : m_xVol ? m_xVol->rename(oldPath, newPath) + : false; + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const char* path) { + return m_fVol ? m_fVol->rmdir(path) : m_xVol ? m_xVol->rmdir(path) : false; + } + //---------------------------------------------------------------------------- + /** \return The volume's cluster size in sectors. */ + Sector_t sectorsPerCluster() const { + return m_fVol ? m_fVol->sectorsPerCluster() + : m_xVol ? m_xVol->sectorsPerCluster() + : 0; + } +#if ENABLE_ARDUINO_SERIAL + //---------------------------------------------------------------------------- + /** List directory contents. + * \return true for success or false for failure. + */ + bool ls() { return ls(&Serial); } + //---------------------------------------------------------------------------- + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags) { return ls(&Serial, flags); } + //---------------------------------------------------------------------------- + /** List the directory contents of a directory to Serial. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + * + * \return true for success or false for failure. + */ + bool ls(const char* path, uint8_t flags = 0) { + return ls(&Serial, path, flags); + } +#endif // ENABLE_ARDUINO_SERIAL +#if ENABLE_ARDUINO_STRING + //---------------------------------------------------------------------------- + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const String& path) { return chdir(path.c_str()); } + //---------------------------------------------------------------------------- + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const String& path) { return exists(path.c_str()); } + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const String& path, bool pFlag = true) { + return mkdir(path.c_str(), pFlag); + } + //---------------------------------------------------------------------------- + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a FsBaseFile object. + */ + FsFile open(const String& path, oflag_t oflag = O_RDONLY); + //---------------------------------------------------------------------------- + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const String& path) { return remove(path.c_str()); } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const String& oldPath, const String& newPath) { + return rename(oldPath.c_str(), newPath.c_str()); + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const String& path) { return rmdir(path.c_str()); } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ +#endif // ENABLE_ARDUINO_STRING + + protected: + newalign_t m_volMem[FS_ALIGN_DIM(ExFatVolume, FatVolume)]; + + private: + /** FsBaseFile allowed access to private members. */ + friend class FsBaseFile; + static FsVolume* cwv() { return m_cwv; } + FsVolume(const FsVolume& from); + FsVolume& operator=(const FsVolume& from); + + static FsVolume* m_cwv; + FatVolume* m_fVol = nullptr; + ExFatVolume* m_xVol = nullptr; +}; diff --git a/third_party/sdfat/src/MinimumSerial.cpp b/third_party/sdfat/src/MinimumSerial.cpp new file mode 100644 index 00000000..2877e740 --- /dev/null +++ b/third_party/sdfat/src/MinimumSerial.cpp @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "MinimumSerial.h" +#if defined(UDR0) || defined(DOXYGEN) +const uint16_t MIN_2X_BAUD = F_CPU / (4 * (2 * 0XFFF + 1)) + 1; +//------------------------------------------------------------------------------ +int MinimumSerial::available() { return UCSR0A & (1 << RXC0) ? 1 : 0; } +//------------------------------------------------------------------------------ +void MinimumSerial::begin(uint32_t baud) { + uint16_t baud_setting; + // don't worry, the compiler will squeeze out F_CPU != 16000000UL + if ((F_CPU != 16000000UL || baud != 57600) && baud > MIN_2X_BAUD) { + // Double the USART Transmission Speed + UCSR0A = 1 << U2X0; + baud_setting = (F_CPU / 4 / baud - 1) / 2; + } else { + // hardcoded exception for compatibility with the bootloader shipped + // with the Duemilanove and previous boards and the firmware on the 8U2 + // on the Uno and Mega 2560. + UCSR0A = 0; + baud_setting = (F_CPU / 8 / baud - 1) / 2; + } + // assign the baud_setting + UBRR0H = baud_setting >> 8; + UBRR0L = baud_setting; + // enable transmit and receive + UCSR0B |= (1 << TXEN0) | (1 << RXEN0); +} +//------------------------------------------------------------------------------ +void MinimumSerial::flush() { + while (((1 << UDRIE0) & UCSR0B) || !(UCSR0A & (1 << UDRE0))) { + } +} +//------------------------------------------------------------------------------ +int MinimumSerial::read() { + if (UCSR0A & (1 << RXC0)) { + return UDR0; + } + return -1; +} +//------------------------------------------------------------------------------ +size_t MinimumSerial::write(uint8_t b) { + while (((1 << UDRIE0) & UCSR0B) || !(UCSR0A & (1 << UDRE0))) { + } + UDR0 = b; + return 1; +} +#endif // defined(UDR0) || defined(DOXYGEN) diff --git a/third_party/sdfat/src/MinimumSerial.h b/third_party/sdfat/src/MinimumSerial.h new file mode 100644 index 00000000..3a7b4bc1 --- /dev/null +++ b/third_party/sdfat/src/MinimumSerial.h @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Minimal AVR Serial driver. + */ +#pragma once +#include "common/SysCall.h" +//============================================================================== +/** + * \class MinimumSerial + * \brief mini serial class for the %SdFat library. + */ +class MinimumSerial : public print_t { + public: + /** \return true for hardware serial */ + operator bool() { return true; } + /** + * \return one if data is available. + */ + int available(); + /** + * Set baud rate for serial port zero and enable in non interrupt mode. + * Do not call this function if you use another serial library. + * \param[in] baud rate + */ + void begin(uint32_t baud); + /** Wait for write done. */ + void flush(); + /** + * Unbuffered read + * \return -1 if no character is available or an available character. + */ + int read(); + /** + * Unbuffered write + * + * \param[in] b byte to write. + * \return 1 + */ + size_t write(uint8_t b) override; + using print_t::write; +}; diff --git a/third_party/sdfat/src/RingBuf.h b/third_party/sdfat/src/RingBuf.h new file mode 100644 index 00000000..abb07a77 --- /dev/null +++ b/third_party/sdfat/src/RingBuf.h @@ -0,0 +1,398 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief Ring buffer for data loggers. + */ +#include "common/FmtNumber.h" +#include "common/SysCall.h" + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Teensy 3.5/3.6 has hard fault at 0x20000000 for unaligned memcpy. +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +inline bool is_aligned(const void* ptr, uintptr_t alignment) { + auto iptr = reinterpret_cast(ptr); + return !(iptr % alignment); +} +inline void memcpyBuf(void* dst, const void* src, size_t len) { + const uint8_t* b = reinterpret_cast(0X20000000UL); + uint8_t* d = reinterpret_cast(dst); + const uint8_t* s = reinterpret_cast(src); + if ((is_aligned(d, 4) && is_aligned(s, 4) && (len & 3) == 0) || + !((d < b && b <= (d + len)) || (s < b && b <= (s + len)))) { + memcpy(dst, src, len); + } else { + while (len--) { + *d++ = *s++; + } + } +} +#else // defined(__MK64FX512__) || defined(__MK66FX1M0__) +inline void memcpyBuf(void* dst, const void* src, size_t len) { + memcpy(dst, src, len); +} +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +#endif // DOXYGEN_SHOULD_SKIP_THIS +/** + * \class RingBuf + * \brief Ring buffer for data loggers and data transmitters. + * + * This ring buffer may be used in ISRs. Use beginISR(), endISR(), write() + * and print() in the ISR and use writeOut() in non-interrupt code + * to write data to a file. + * + * Use beginISR(), endISR() and read() in an ISR with readIn() in non-interrupt + * code to provide file data to an ISR. + */ +template +class RingBuf : public Print { + public: + /** + * RingBuf Constructor. + */ + RingBuf() { begin(nullptr); } + /** + * Initialize RingBuf. + * \param[in] file Underlying file. + */ + void begin(F* file) { + m_file = file; + m_count = 0; + m_head = 0; + m_tail = 0; + m_inISR = false; + clearWriteError(); + } + /** + * Disable protection of m_count by noInterrupts()/interrupts. + */ + void beginISR() { m_inISR = true; } + /** + * \return the RingBuf free space in bytes. + */ + size_t bytesFree() const { return Size - bytesUsed(); } + /** + * \return the RingBuf used space in bytes. + */ + size_t bytesUsed() const { + if (m_inISR) { + return m_count; + } else { + noInterrupts(); + size_t rtn = m_count; + interrupts(); + return rtn; + } + } + /** + * Enable protection of m_count by noInterrupts()/interrupts. + */ + void endISR() { m_inISR = false; } +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // See write(), read(), beginISR() and endISR(). + size_t __attribute__((error("use write(buf, count), beginISR(), endISR()"))) + memcpyIn(const void* buf, size_t count); + size_t __attribute__((error("use read(buf, count), beginISR(), endISR()"))) + memcpyOut(void* buf, size_t count); +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, value, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + char sign = 0; + char buf[3 * sizeof(Type) + 3]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + if (value < 0) { + value = -value; + sign = '-'; + } + if (sizeof(Type) < 4) { + str = fmtBase10(str, static_cast(value)); + } else { + str = fmtBase10(str, static_cast(value)); + } + if (sign) { + *--str = sign; + } + return write((const uint8_t*)str, &buf[sizeof(buf)] - str); + } + /** Read data from RingBuf. + * \param[out] buf destination for data. + * \param[in] count number of bytes to read. + * \return Actual count of bytes read. + */ + size_t read(void* buf, size_t count) { + size_t n = bytesFree(); + if (count > n) { + count = n; + } + uint8_t* dst = reinterpret_cast(buf); + n = minSize(Size - m_tail, count); + if (n == count) { + memcpyBuf(dst, m_buf + m_tail, n); + m_tail = advance(m_tail, n); + } else { + memcpyBuf(dst, m_buf + m_tail, n); + memcpyBuf(dst + n, m_buf, count - n); + m_tail = count - n; + } + adjustCount(-count); + return count; + } + /** + * Efficient read for small types. + * + * \param[in] data location for data item. + * \return true for success else false. + */ + template + bool read(Type* data) { + if (bytesUsed() < sizeof(Type)) { + return false; + } + uint8_t* ptr = reinterpret_cast(data); + for (size_t i = 0; i < sizeof(Type); i++) { + ptr[i] = m_buf[m_tail]; + m_tail = advance(m_tail); + } + adjustCount(-sizeof(Type)); + return true; + } + /** + * Read data into the RingBuf from the underlying file. + * the number of bytes read may be less than count if + * bytesFree is less than count. + * + * This function must not be used in an ISR. + * + * \param[in] count number of bytes to be read. + * \return Number of bytes actually read or negative for read error. + */ + int readIn(size_t count) { + size_t n = bytesFree(); + if (count > n) { + count = n; + } + n = minSize(Size - m_head, count); + auto rtn = m_file->read(m_buf + m_head, n); + if (rtn <= 0) { + return rtn; + } + size_t nread = rtn; + if (n < count && nread == n) { + rtn = m_file->read(m_buf, count - n); + if (rtn > 0) { + nread += rtn; + } + } + m_head = advance(m_head, nread); + adjustCount(nread); + return nread; + } + /** + * Write all data in the RingBuf to the underlying file. + * \return true for success. + */ + bool sync() { + size_t n = bytesUsed(); + return n ? writeOut(n) == n : true; + } + /** + * Copy data to the RingBuf from buf. + * + * No data will be copied if count is greater than bytesFree. + * Use getWriteError() to check for print errors and + * clearWriteError() to clear the error. + * + * \param[in] buf Location of data to be written. + * \param[in] count number of bytes to be written. + * \return Number of bytes actually written. + */ + size_t write(const void* buf, size_t count) { + if (bytesFree() < count) { + setWriteError(); + return 0; + } + const uint8_t* src = (const uint8_t*)buf; + size_t n = minSize(Size - m_head, count); + if (n == count) { + memcpyBuf(m_buf + m_head, src, n); + m_head = advance(m_head, n); + } else { + memcpyBuf(m_buf + m_head, src, n); + memcpyBuf(m_buf, src + n, count - n); + m_head = count - n; + } + adjustCount(count); + return count; + } + /** + * Copy str to RingBuf. + * + * \param[in] str Location of data to be written. + * \return Number of bytes actually written. + */ + size_t write(const char* str) { return Print::write(str); } + /** + * Override virtual function in Print for efficiency. + * + * \param[in] buf Location of data to be written. + * \param[in] count number of bytes to be written. + * \return Number of bytes actually written. + */ + size_t write(const uint8_t* buf, size_t count) override { + return write((const void*)buf, count); + } + /** + * Efficient write for small types. + * \param[in] data Item to be written. + * \return Number of bytes actually written. + */ + template + size_t write(Type data) { + uint8_t* ptr = reinterpret_cast(&data); + if (bytesFree() < sizeof(Type)) { + setWriteError(); + return 0; + } + for (size_t i = 0; i < sizeof(Type); i++) { + m_buf[m_head] = ptr[i]; + m_head = advance(m_head); + } + adjustCount(sizeof(Type)); + return sizeof(Type); + } + /** + * Required function for Print. + * \param[in] data Byte to be written. + * \return Number of bytes actually written. + * + * Try to force devirtualization by using final and always_inline. + */ + size_t write(uint8_t data) final __attribute__((always_inline)) { + // Use this if above does not compile size_t write(uint8_t data) final { + return write(data); + } + /** + * Write data to file from RingBuf buffer. + * \param[in] count number of bytes to be written. + * + * The number of bytes written may be less than count if + * bytesUsed is less than count or if an error occurs. + * + * This function must only be used in non-interrupt code. + * + * \return Number of bytes actually written. + */ + size_t writeOut(size_t count) { + size_t n = bytesUsed(); // Protected from interrupts; + if (count > n) { + count = n; + } + n = minSize(Size - m_tail, count); + auto rtn = m_file->write(m_buf + m_tail, n); + if (rtn <= 0) { + return 0; + } + size_t nwrite = rtn; + if (n < count && nwrite == n) { + rtn = m_file->write(m_buf, count - n); + if (rtn > 0) { + nwrite += rtn; + } + } + m_tail = advance(m_tail, nwrite); + adjustCount(-nwrite); + return nwrite; + } + + private: + uint8_t __attribute__((aligned(4))) m_buf[Size]; + F* m_file; + volatile size_t m_count; + size_t m_head; + size_t m_tail; + volatile bool m_inISR; + + void adjustCount(int amount) { + if (m_inISR) { + m_count += amount; + } else { + noInterrupts(); + m_count += amount; + interrupts(); + } + } + size_t advance(size_t index) { + if (!((Size - 1) & Size)) { + return (index + 1) & (Size - 1); + } + return index + 1 < Size ? index + 1 : 0; + } + size_t advance(size_t index, size_t n) { + index += n; + return index < Size ? index : index - Size; + } + // avoid macro MIN + size_t minSize(size_t a, size_t b) { return a < b ? a : b; } +}; diff --git a/third_party/sdfat/src/SdCard/PioSdio/CPPLINT.cfg b/third_party/sdfat/src/SdCard/PioSdio/CPPLINT.cfg new file mode 100644 index 00000000..218ac2bf --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/CPPLINT.cfg @@ -0,0 +1 @@ +exclude_files=PioSdioCard.pio.h diff --git a/third_party/sdfat/src/SdCard/PioSdio/DbgLog.h b/third_party/sdfat/src/SdCard/PioSdio/DbgLog.h new file mode 100644 index 00000000..b89da3b1 --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/DbgLog.h @@ -0,0 +1,243 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Classes for Debug messages. + */ +#pragma once +#include "Printable.h" + +/** Character used in Num class for HEX format */ +#define NUM64_HEX_A 'a' + +/** Enable or disable debug messages */ +#define ENABLE_DBG_MSG 0 + +/** Port for debug messages */ +#define DBG_LOG_PORT Serial + +/** Filename to print in message. */ +#if defined(DBG_FILE) +#elif defined(__FILE_NAME__) +#define DBG_FILE __FILE_NAME__ +#else +#define DBG_FILE __FILE__ +#endif + +/** Macro for debug messages */ +#if ENABLE_DBG_MSG +#define DBG_MSG(...) \ + do { \ + logmsgln(F(DBG_FILE), ":", __LINE__, " ", ##__VA_ARGS__); \ + } while (0) +#else // ENABLE_DBG_MSG +#define DBG_MSG(...) \ + do { \ + } while (0) +#endif // ENABLE_DBG_MSG + +/** + * \class Bin + * \brief Print binary with byte and nibble separators. + */ +class Bin : public Printable { + uint32_t n_; + uint8_t p_; + // Bin not supported for 64-bits. + // cppcheck-suppress-begin uninitMemberVarPrivate + explicit Bin(int64_t, uint8_t = 0) {} + explicit Bin(uint64_t, uint8_t = 0) {} + // cppcheck-suppress-end uninitMemberVarPrivate + public: + template + /** + * \param[in] n Number to print. + * \param[in] p Precision. + */ + explicit Bin(T n, uint8_t p = 0) : n_(n), p_(p) {} + /** + * \param[in] pr Print stream. + * \return Number of bytes printed. + */ + size_t printTo(Print& pr) const { + auto n = n_; + uint8_t p = p_ > 8 * sizeof(n) ? 8 * sizeof(n) : p_; + char buf[10 * sizeof(n) + 1]; + char* end = buf + sizeof(buf); + char* str = end; + uint8_t i = 0; + do { + if (i && (i % 4) == 0) { + *--str = i % 8 ? '\'' : '|'; + } + *--str = n & 1 ? '1' : '0'; + n /= 2; + i++; + } while (n || i < p); + return pr.write(str, end - str); + } +}; +/** + * \class Dbl + * \brief Print floating point with precision. + */ +class Dbl : public Printable { + double n_; + int p_; + + public: + /** + * \param[in] n Number to print. + * \param[in] p Precision. + */ + Dbl(double n, int p) : n_(n), p_(p) {} + /** + * \param[in] pr Print stream. + * \return Number of bytes printed. + */ + size_t printTo(Print& pr) const { return pr.print(n_, p_); } +}; +/** + * \class Hex + * \brief Print in hex format. + */ +#if __cplusplus > 201700L +template +class Hex : public Printable { + T n_; + + public: + /** + * \param[in] n Number to print. + */ + explicit Hex(T n) : n_(n) {} + /** + * \param[in] pr Print stream. + * \return Number of bytes printed. + */ + size_t printTo(Print& pr) const { return pr.print(n_, HEX); } +}; +#else // __cplusplus > 201700L +class Hex : public Printable { + uint32_t n_; + // No 64-bit support unless C++17 or better. + explicit Hex(int64_t) {} + explicit Hex(uint64_t) {} + + public: + /** + * \param[in] n Number to print. + */ + template + explicit Hex(T n) : n_(n) {} + /** + * \param[in] pr Print stream. + * \return Number of bytes printed. + */ + size_t printTo(Print& pr) const { return pr.print(n_, HEX); } +}; +#endif // __cplusplus > 201700L +/** + * \class Num + * \brief Print 64-bit print, lower case hex, alt binary or precision. + */ +class Num : public Printable { + uint64_t n_; + uint8_t b_; + uint8_t p_; + char s_; + + public: + /** + * \param[in] n Number to print. + * \param[in] b Base. + * \param[in] p Precision. + * \param[in] s Sign. + */ + template + explicit Num(T n, uint8_t b = 0, uint8_t p = 0, char s = 0) : b_(b), p_(p) { + n_ = b == 10 && n < 0 ? -n : n; + s_ = b == 10 && n < 0 ? '-' : s; + } + /** + * \param[in] pr Print stream. + * \return Number of bytes printed. + */ + size_t printTo(Print& pr) const { + char buf[8 * sizeof(uint64_t) + 1]; + char* end = buf + sizeof(buf); + char* str = end; + uint64_t n = n_; + uint8_t p = p_ > 8 * sizeof(n) ? 8 * sizeof(n) : p_; + uint8_t base = b_ < 2 || b_ > 16 ? 10 : b_; + uint8_t i = 0; + do { + uint8_t d = n % base; + *--str = d < 10 ? d + '0' : d + NUM64_HEX_A - 10; + n /= base; + i++; + } while (n || i < p); + if (s_) { + *--str = s_; + } + return pr.write(str, end - str); + } +}; +/** + * \param[in] arg Item to print. + * \return Number of bytes printed. + */ +template +inline size_t logmsg(T arg) { + return DBG_LOG_PORT.print(arg); +} +/** + * \param[in] b Item to print. + * \return Number of bytes printed. + */ +inline size_t logmsg(bool b) { return logmsg(b ? F("true") : F("false")); } + +/** \return Zero to end recursive template */ +inline size_t logmsg() { return 0; } + +/** + * \param[in] var1 Next item to print. + * \param[in] vars Rest of items to print. + * \return number of bytes printed. + */ +template +inline size_t logmsg(T var1, Types... vars) { + size_t n = logmsg(var1); + return n += logmsg(vars...); +} +/** + * \param[in] vars List of items to print. + * \return number of bytes printed. + */ +template +inline size_t logmsgln(Types... vars) { + size_t n = logmsg(vars...); + return n + logmsg("\r\n"); // cppcheck-suppress incorrectStringBooleanError +} diff --git a/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.cpp b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.cpp new file mode 100644 index 00000000..c69e53ad --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.cpp @@ -0,0 +1,1004 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifdef ARDUINO_ARCH_RP2040 +#include + +#include // Required for std::max, std::min +#define DEBUG_FILE "PioSdioCard.cpp" +#include "../SdCardInfo.h" +#include "DbgLog.h" +#include "PioSdioCard.h" +#include "PioSdioCard.pio.h" +//------------------------------------------------------------------------------ +// USE_DEBUG_MODE 0 - no debug, 1 - print message, 2 - Use scope/analyzer. +#define USE_DEBUG_MODE 1 + +const uint PIO_CLK_DIV_RUN = 1; + +const uint PIN_SDIO_UNDEFINED = 63u; + +const uint DAT_FIFO_DEPTH = 8; +//============================================================================== +// Command definitions. +enum { RSP_R0 = 0, RSP_R1 = 1, RSP_R2 = 2, RSP_R3 = 3, RSP_R6 = 6, RSP_R7 = 7 }; +static const CmdRsp_t CMD0_R0(CMD0, RSP_R0); +static const CmdRsp_t CMD2_R2(CMD2, RSP_R2); +static const CmdRsp_t CMD3_R6(CMD3, RSP_R6); +static const CmdRsp_t CMD6_R1(CMD6, RSP_R1); +static const CmdRsp_t CMD7_R1(CMD7, RSP_R1); +static const CmdRsp_t CMD8_R7(CMD8, RSP_R7); +static const CmdRsp_t CMD9_R2(CMD9, RSP_R2); +static const CmdRsp_t CMD10_R2(CMD10, RSP_R2); +static const CmdRsp_t CMD12_R1(CMD12, RSP_R1); +static const CmdRsp_t CMD13_R1(CMD13, RSP_R1); +static const CmdRsp_t CMD18_R1(CMD18, RSP_R1); +static const CmdRsp_t CMD25_R1(CMD25, RSP_R1); +static const CmdRsp_t CMD32_R1(CMD32, RSP_R1); +static const CmdRsp_t CMD33_R1(CMD33, RSP_R1); +static const CmdRsp_t CMD38_R1(CMD38, RSP_R1); +static const CmdRsp_t CMD55_R1(CMD55, RSP_R1); +static const CmdRsp_t ACMD6_R1(ACMD6, RSP_R1); +static const CmdRsp_t ACMD13_R1(ACMD13, RSP_R1); +static const CmdRsp_t ACMD41_R3(ACMD41, RSP_R3); +static const CmdRsp_t ACMD51_R1(ACMD51, RSP_R1); +//============================================================================== +class Timeout { + public: + explicit Timeout(uint ms) : _usStart(0), _usTimeout(1000 * ms) {} + bool timedOut() { + if (_usStart) { + return (usSinceBoot() - _usStart) > _usTimeout; + } + _usStart = usSinceBoot(); + return false; + } + uint32_t usSinceBoot() { return to_us_since_boot(get_absolute_time()); } + uint32_t _usStart; + uint32_t _usTimeout; +}; +//============================================================================== +#if USE_DEBUG_MODE +//------------------------------------------------------------------------------ +static inline void gpioStatus(uint gpio) { + logmsgln("gpio", gpio, " drive: ", gpio_get_drive_strength(gpio)); + // logmsgln("gpio", gpio, + // " drive: ", static_cast(gpio_get_drive_strength(gpio))); + logmsgln("gpio", gpio, " slew: ", gpio_get_slew_rate(gpio)); + logmsgln("gpio", gpio, " hyst: ", gpio_is_input_hysteresis_enabled(gpio)); + logmsgln("gpio", gpio, " pull: ", gpio_is_pulled_up(gpio)); +} +//------------------------------------------------------------------------------ +static inline void pioRegs(PIO pio) { + logmsgln("ctrl: 0b", Bin(pio->ctrl)); + logmsgln("fstat: 0b", Bin(pio->fstat)); + logmsgln("fdebug: 0b", Bin(pio->fdebug)); + logmsgln("flevel: 0b", Bin(pio->flevel)); + logmsgln("padout: 0b", Bin(pio->dbg_padout)); + logmsgln("padoe: 0b", Bin(pio->dbg_padoe)); + logmsgln("cfginfo: 0x", Hex(pio->dbg_cfginfo)); + logmsgln("sync_bypass: 0b", Bin(pio->input_sync_bypass)); +} +//------------------------------------------------------------------------------ +static inline void pioSmRegs(PIO pio, uint sm) { + logmsgln("sm", sm, " clkdiv: 0x", Hex(pio->sm[sm].clkdiv)); + logmsgln("sm", sm, " execctrl: 0x", Hex(pio->sm[sm].execctrl)); + logmsgln("sm", sm, " shiftctrl: 0x", Hex(pio->sm[sm].shiftctrl)); + logmsgln("sm", sm, " addr: 0x", Hex(pio->sm[sm].addr)); + logmsgln("sm", sm, " pinctrl: 0x", Hex(pio->sm[sm].pinctrl)); +} +//------------------------------------------------------------------------------ +#endif // USE_DEBUG_MODE +#define sdError(code) \ + { \ + setSdErrorCode(code, __LINE__); \ + DBG_MSG(#code); \ + } +#define SDIO_FAIL() DBG_MSG("SDIO_FAIL") +//============================================================================== +// CRC functions. +//------------------------------------------------------------------------------ +// See this library's extras folder. +static const uint8_t crc7_table[256] = { + 0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, // 00 - 07 + 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee, // 08 - 0f + 0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, // 10 - 17 + 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc, // 18 - 1f + 0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, // 20 - 27 + 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a, // 28 - 2f + 0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, // 30 - 37 + 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8, // 38 - 3f + 0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, // 40 - 47 + 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26, // 48 - 4f + 0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, // 50 - 57 + 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14, // 58 - 5f + 0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, // 60 - 67 + 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42, // 68 - 6f + 0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, // 70 - 77 + 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70, // 78 - 7f + 0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, // 80 - 87 + 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c, // 88 - 8f + 0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, // 90 - 97 + 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e, // 98 - 9f + 0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, // a0 - a7 + 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08, // a8 - af + 0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, // b0 - b7 + 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a, // b8 - bf + 0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, // c0 - c7 + 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4, // c8 - cf + 0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, // d0 - d7 + 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96, // d8 - df + 0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, // e0 - e7 + 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0, // e8 - ef + 0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, // f0 - f7 + 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2 // f8 - ff +}; +//------------------------------------------------------------------------------ +inline static uint8_t CRC7(const uint8_t* data, uint8_t n) { + uint8_t crc = 0; + for (uint8_t i = 0; i < n; i++) { + crc = crc7_table[crc ^ data[i]]; + } + return crc | 1; +} +//------------------------------------------------------------------------------ +// Modified from sdio_crc16_4bit_checksum() in +// https://github.com/ZuluSCSI/ZuluSCSI-firmware +// +static inline __attribute__((always_inline)) uint64_t crc16(uint64_t crc, + uint32_t data_in) { + // Shift out 8 bits for each line + uint32_t data_out = crc >> 32; + crc <<= 32; + + // XOR outgoing data to itself with 4 bit delay + data_out ^= (data_out >> 16); + + // XOR incoming data to outgoing data with 4 bit delay + data_out ^= (data_in >> 16); + + // XOR outgoing and incoming data to accumulator at each tap + uint64_t xorred = data_out ^ data_in; + crc ^= xorred; + crc ^= xorred << (5 * 4); + crc ^= xorred << (12 * 4); + return crc; +} +//------------------------------------------------------------------------------ +static bool claimPio(PIO pio, const pio_program_t* program) { + uint mask = 0; + if (!pio_can_add_program(pio, program)) { + DBG_MSG("pio_can_add_program"); + return false; + } + for (uint i = 0; i < NUM_PIO_STATE_MACHINES; i++) { + int sm = pio_claim_unused_sm(pio, false); + if (sm < 0) { + break; + } + mask |= 1u << sm; + } + if (mask == ((1u << NUM_PIO_STATE_MACHINES) - 1)) { + return true; + } + for (uint sm = 0; sm < NUM_PIO_STATE_MACHINES; sm++) { + if ((1u << sm) & mask) { + pio_sm_unclaim(pio, sm); + } + } + DBG_MSG("pio_can_add_program"); + return false; +} +//============================================================================== +// add to PioSdioCard class int the future. +// PioSdioCard::PioSdioCard() +// PioSdioCard::~PioSdioCard() +//------------------------------------------------------------------------------ +bool PioSdioCard::cardAcmd(uint32_t rca, CmdRsp_t cmdRsp, uint32_t arg) { + return cardCommand(CMD55_R1, rca) && cardCommand(cmdRsp, arg); +} +//------------------------------------------------------------------------------ +bool PioSdioCard::begin(PioSdioConfig sdioConfig) { + pioEnd(); + Timeout timeout(SD_INIT_TIMEOUT); + uint32_t arg; + m_curState = IDLE_STATE; + m_errorCode = SD_CARD_ERROR_NONE; + m_highCapacity = false; + m_initDone = false; + m_version2 = false; + m_clkPin = sdioConfig.clkPin(); + m_cmdPin = sdioConfig.cmdPin(); + m_dat0Pin = sdioConfig.dat0Pin(); + + // Four PIO cycles per SD CLK cycle. + m_clkDiv = ceil((0.00025 * clock_get_hz(clk_sys)) / SD_MAX_INIT_RATE_KHZ); + +#if USE_DEBUG_MODE == 2 + Serial.println(); + pioRegs(m_pio); + pioSmRegs(m_pio, m_sm0); + pioSmRegs(m_pio, m_sm1); + gpioStatus(m_clkPin); + gpioStatus(m_cmdPin); + while (Serial.read() >= 0) { + } + Serial.println("Logic Analyzer on then type any char"); + while (!Serial.available()) { + } +#endif // USE_DEBUG_MODE + // A few cards still require clocks after power-up. + powerUpClockCycles(); + if (!pioInit()) { + goto fail; + } + pioConfig(m_clkDiv); + if (!cardCommand(CMD0_R0, 0)) { + sdError(SD_CARD_ERROR_CMD0); + goto fail; + } + if (cardCommand(CMD8_R7, 0X1AA)) { + if (m_cardRsp != 0X1AA) { + sdError(SD_CARD_ERROR_CMD8); + goto fail; + } + m_version2 = true; + } else { + m_version2 = false; + m_errorCode = SD_CARD_ERROR_NONE; + } + arg = m_version2 ? 0X40300000 : 0x00300000; + while (true) { + if (!cardAcmd(0, ACMD41_R3, arg)) { + sdError(SD_CARD_ERROR_ACMD41); + goto fail; + } + if (m_cardRsp & 0x80000000) { + break; + } + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_ACMD41); + goto fail; + } + } + m_ocr = m_cardRsp; + if (m_cardRsp & 0x40000000) { + // Is high capacity. + m_highCapacity = true; + } + if (!cardCommand(CMD2_R2, 0)) { + sdError(SD_CARD_ERROR_CMD2); + goto fail; + } + if (!cardCommand(CMD3_R6, 0)) { + sdError(SD_CARD_ERROR_CMD3); + goto fail; + } + m_rca = m_cardRsp & 0xFFFF0000; + if (!cardCommand(CMD9_R2, m_rca, &m_csd)) { + sdError(SD_CARD_ERROR_CMD9); + goto fail; + } + if (!cardCommand(CMD10_R2, m_rca, &m_cid)) { + sdError(SD_CARD_ERROR_CMD10); + goto fail; + } + if (!cardCommand(CMD7_R1, m_rca)) { + sdError(SD_CARD_ERROR_CMD7); + goto fail; + } + + if (!cardAcmd(m_rca, ACMD6_R1, 2)) { + sdError(SD_CARD_ERROR_ACMD6); + goto fail; + } + m_clkDiv = sdioConfig.clkDiv(); + pioConfig(m_clkDiv); + if (!cardAcmd(m_rca, ACMD51_R1, 0)) { + sdError(SD_CARD_ERROR_ACMD51); + goto fail; + } + if (!readData(&m_scr, sizeof(m_scr))) { + DBG_MSG("readData"); + goto fail; + } + if (!cardAcmd(m_rca, ACMD13_R1, 0)) { + sdError(SD_CARD_ERROR_ACMD13); + SDIO_FAIL(); + goto fail; + } + + if (!readData(&m_sds, sizeof(m_sds))) { + SDIO_FAIL(); + goto fail; + } + m_initDone = true; + + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::cardCMD6(uint32_t arg, uint8_t* status) { + if (!cardCommand(CMD6_R1, arg)) { + sdError(SD_CARD_ERROR_CMD6); + goto fail; + } + if (!readData(status, 64)) { + SDIO_FAIL(); + goto fail; + } + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::cardCommand(CmdRsp_t cmd, uint32_t arg, void* rsp) { + uint8_t buf[6]; + uint nRsp = cmd.rsp == RSP_R0 ? 0 : cmd.rsp == RSP_R2 ? 17 : 6; + const io_ro_8* rxFifo = reinterpret_cast(&m_pio->rxf[m_sm0]); + io_wo_8* txFifo = reinterpret_cast(&m_pio->txf[m_sm0]); + pio_sm_set_enabled(m_pio, m_sm1, false); + pio_sm_init(m_pio, m_sm0, m_cmdRspOffset, &m_cmdConfig); + pio_sm_exec(m_pio, m_sm0, pio_encode_set(pio_pindirs, 1)); + *txFifo = 55; + pio_sm_exec(m_pio, m_sm0, pio_encode_out(pio_x, 8)); + *txFifo = nRsp ? 8 * nRsp - 1 : 0; + pio_sm_exec(m_pio, m_sm0, pio_encode_out(pio_y, 8)); + pio_sm_set_enabled(m_pio, m_sm0, true); + uint n = 0; + buf[n++] = (uint8_t)(cmd.idx | 0x40); + buf[n++] = (uint8_t)(arg >> 24U); + buf[n++] = (uint8_t)(arg >> 16U); + buf[n++] = (uint8_t)(arg >> 8U); + buf[n++] = (uint8_t)arg; + buf[n++] = CRC7(buf, 5); + *txFifo = 0XFF; + for (uint i = 0; i < n; i++) { + while (pio_sm_is_tx_fifo_full(m_pio, m_sm0)) { + } + *txFifo = buf[i]; + } + + Timeout timeout(SD_CMD_TIMEOUT); + if (!nRsp) { + uint32_t fdebug_tx_stall = 1u << (PIO_FDEBUG_TXSTALL_LSB + m_sm0); + m_pio->fdebug = fdebug_tx_stall; + while (!(m_pio->fdebug & fdebug_tx_stall)) { + if (timeout.timedOut()) { + goto fail; + } + } + goto done; + } + uint8_t rtn[20]; + + for (uint i = 0; i < nRsp; i++) { + while (pio_sm_is_rx_fifo_empty(m_pio, m_sm0)) { + if (timeout.timedOut()) { + goto fail; + } + } + rtn[i] = *rxFifo; + } + if (cmd.rsp == RSP_R3) { + if (rtn[0] != 0X3F || rtn[5] != 0XFF) { + goto fail; + } + } else { + uint8_t crc; + if (cmd.rsp == RSP_R2) { + crc = CRC7(rtn + 1, nRsp - 2); + } else { + crc = CRC7(rtn, nRsp - 1); + } + if (rtn[nRsp - 1] != crc) { +#if USE_DEBUG_MODE + Serial.printf("CHK: %02X, CRC: %02X\n", rtn[nRsp - 1], crc); + for (uint i = 0; i < nRsp; i++) { + Serial.printf(" %02X", rtn[i]); + } + Serial.println(); +#endif // USE_DEBUG_MODE + sdError(SD_CARD_ERROR_READ_CRC); + goto fail; + } + } + if (nRsp == 6) { + m_cardRsp = (rtn[1] << 24) | (rtn[2] << 16) | (rtn[3] << 8) | rtn[4]; + if (rsp) { + *reinterpret_cast(rsp) = m_cardRsp; + } + } else if (rsp && nRsp == 17) { + memcpy(rsp, rtn + 1, 16); + } + +done: + pio_sm_set_enabled(m_pio, m_sm0, false); + return true; + +fail: +#if USE_DEBUG_MODE + DBG_MSG("CMD", cmd.idx, " failed"); +#endif // USE_DEBUG_MODE + pio_sm_set_enabled(m_pio, m_sm0, false); + return false; +} +//------------------------------------------------------------------------------ +void PioSdioCard::end() { pioEnd(); } +//------------------------------------------------------------------------------ +bool PioSdioCard::erase(uint32_t firstSector, uint32_t lastSector) { + Timeout timeout(SD_ERASE_TIMEOUT); + if (!syncDevice()) { + SDIO_FAIL(); + goto fail; + } + // check for single sector erase + if (!m_csd.eraseSingleBlock()) { + // erase size mask + uint8_t m = m_csd.eraseSize() - 1; + if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) { + // error card can't erase specified area + sdError(SD_CARD_ERROR_ERASE_SINGLE_SECTOR); + goto fail; + } + } + if (!m_highCapacity) { + firstSector <<= 9; + lastSector <<= 9; + } + if (!cardCommand(CMD32_R1, firstSector)) { + sdError(SD_CARD_ERROR_CMD32); + goto fail; + } + if (!cardCommand(CMD33_R1, lastSector)) { + sdError(SD_CARD_ERROR_CMD33); + goto fail; + } + if (!cardCommand(CMD38_R1, 0)) { + sdError(SD_CARD_ERROR_CMD38); + goto fail; + } + while (isBusy()) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_ERASE_TIMEOUT); + goto fail; + } + } + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +uint8_t PioSdioCard::errorCode() const { return m_errorCode; } +//------------------------------------------------------------------------------ +uint32_t PioSdioCard::errorData() const { return m_cardRsp; } +//------------------------------------------------------------------------------ +uint32_t PioSdioCard::errorLine() const { return m_errorLine; } +//------------------------------------------------------------------------------ +bool PioSdioCard::isBusy() { + return gpio_get(m_dat0Pin) ? false : !(status() & CARD_STATUS_READY_FOR_DATA); +} +//------------------------------------------------------------------------------ +void PioSdioCard::pioConfig(float clkDiv) { + m_cmdConfig = + pio_cmd_rsp_program_config(m_cmdRspOffset, m_cmdPin, m_clkPin, clkDiv); + m_rdClkConfig = + pio_rd_clk_program_config(m_rdClkOffset, m_dat0Pin, m_clkPin, clkDiv); + m_rdDataConfig = + pio_rd_data_program_config(m_rdDataOffset, m_dat0Pin, clkDiv); + m_wrDataConfig = + pio_wr_data_program_config(m_wrDataOffset, m_dat0Pin, m_clkPin, clkDiv); + m_wrRespConfig = + pio_wr_resp_program_config(m_wrRespOffset, m_dat0Pin, m_clkPin, clkDiv); +} +//------------------------------------------------------------------------------ +void PioSdioCard::pioEnd() { + if (!m_pio) { + return; + } + for (uint sm = 0; sm < NUM_PIO_STATE_MACHINES; sm++) { + pio_sm_unclaim(m_pio, sm); + } + if (m_cmdRspOffset >= 0) { + pio_remove_program(m_pio, &cmd_rsp_program, m_cmdRspOffset); + m_cmdRspOffset = -1; + } + if (m_rdClkOffset >= 0) { + pio_remove_program(m_pio, &rd_clk_program, m_rdClkOffset); + m_rdClkOffset = -1; + } + if (m_rdDataOffset >= 0) { + pio_remove_program(m_pio, &rd_data_program, m_rdDataOffset); + m_rdDataOffset = -1; + } + if (m_wrDataOffset >= 0) { + pio_remove_program(m_pio, &wr_data_program, m_wrDataOffset); + m_wrDataOffset = -1; + } + if (m_wrRespOffset >= 0) { + pio_remove_program(m_pio, &wr_resp_program, m_wrRespOffset); + m_wrRespOffset = -1; + } + m_pio = nullptr; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::pioInit() { + uint pin[] = {m_clkPin, m_cmdPin, m_dat0Pin, + m_dat0Pin + 1, m_dat0Pin + 2, m_dat0Pin + 3}; + uint16_t pio_instructions[PIO_INSTRUCTION_COUNT]; + pio_program_t pio_program = {.instructions = nullptr, + .length = PIO_INSTRUCTION_COUNT, + .origin = -1, + .pio_version = 0, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif + }; + if (claimPio(pio0, &pio_program)) { + m_pio = pio0; + } else if (claimPio(pio1, &pio_program)) { + m_pio = pio1; +#if NUM_PIOS > 2 + } else if (claimPio(pio2, &pio_program)) { + m_pio = pio2; +#endif + } else { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + m_sm0 = 0; + m_sm1 = 1; +#if PICO_PIO_USE_GPIO_BASE + if (std::max({m_clkPin, m_cmdPin, m_dat0Pin + 3}) > 31) { + if (std::min({m_clkPin, m_cmdPin, m_dat0Pin}) < 16 || + pio_set_gpio_base(m_pio, 16) != PICO_OK) { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + } else if (pio_set_gpio_base(m_pio, 0) != PICO_OK) { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } +#endif // PICO_PIO_USE_GPIO_BASE + rd_data_patch_program(&pio_program, pio_instructions, m_clkPin); + m_rdDataOffset = pio_add_program(m_pio, &pio_program); + if (m_rdDataOffset < 0) { + DBG_MSG("m_rdDataOffset: ", m_rdDataOffset); + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + m_cmdRspOffset = pio_add_program(m_pio, &cmd_rsp_program); + if (m_cmdRspOffset < 0) { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + m_rdClkOffset = pio_add_program(m_pio, &rd_clk_program); + if (m_rdClkOffset < 0) { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + m_wrDataOffset = pio_add_program(m_pio, &wr_data_program); + if (m_wrDataOffset < 0) { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + m_wrRespOffset = pio_add_program(m_pio, &wr_resp_program); + if (m_wrRespOffset < 0) { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + for (uint i = 0; i < 6U; i++) { + gpio_pull_up(pin[i]); + } + gpio_set_drive_strength(m_clkPin, GPIO_DRIVE_STRENGTH_8MA); + gpio_set_slew_rate(m_clkPin, GPIO_SLEW_RATE_FAST); + + for (uint i = 0; i < 6U; i++) { + pio_gpio_init(m_pio, pin[i]); + } + m_pio->input_sync_bypass |= + (1 << m_clkPin) | (1 << m_cmdPin) | (0XF << m_dat0Pin); + if (pio_sm_set_consecutive_pindirs(m_pio, m_sm0, m_clkPin, 1, true) != + PICO_OK || + pio_sm_set_consecutive_pindirs(m_pio, m_sm0, m_cmdPin, 1, true) != + PICO_OK || + pio_sm_set_consecutive_pindirs(m_pio, m_sm0, m_dat0Pin, 4, false) != + PICO_OK) { + sdError(SD_CARD_ERROR_ADD_PIO_PROGRAM); + goto fail; + } + return true; + +fail: + pioEnd(); + return false; +} +//------------------------------------------------------------------------------ +// A few cards still need at least 74 clocks after power-up. from the spec: +// +// After 1 ms VDD stable time, host provides at least 74 clocks while keeping +// CMD high before issuing the first command. In the case of SPI mode, CS +// shall be held high during 74 clock cycles. +// +// The original April 15, 2001 spec explains the 74 clocks: +// +// The additional 10 clocks (over the 64 clocks after which the card should be +// ready for communication) is provided to eliminate power-up synchronization +// problems. +// +void PioSdioCard::powerUpClockCycles() { + // Two clk_sys per SD clock cycle. + uint32_t nWait = ceil(0.0005 * clock_get_hz(clk_sys) / SD_MAX_INIT_RATE_KHZ); + gpio_init(m_cmdPin); + gpio_set_drive_strength(m_cmdPin, GPIO_DRIVE_STRENGTH_8MA); + gpio_set_dir(m_cmdPin, true); + gpio_put(m_cmdPin, 1); + gpio_init(m_clkPin); + gpio_set_drive_strength(m_clkPin, GPIO_DRIVE_STRENGTH_8MA); + gpio_set_dir(m_clkPin, true); + + // Send 80 SD CLK cycles with CMD high. End with CLK low. + for (uint i = 0; i <= 160; i++) { + gpio_put(m_clkPin, 1 & i); + busy_wait_at_least_cycles(nWait); + } +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readCID(cid_t* cid) { + memcpy(cid, &m_cid, sizeof(cid_t)); + return true; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readCSD(csd_t* csd) { + memcpy(csd, &m_csd, sizeof(csd_t)); + return true; +} +//------------------------------------------------------------------------------ +bool __time_critical_func(PioSdioCard::readData)(void* dst, size_t count) { + uint32_t buf[128]; + uint n32 = count / 4; + uint nr = n32 + 2; + const uint mask = (1ul << m_sm0) | (1ul << m_sm1); + io_wo_8* txFifo = reinterpret_cast(&m_pio->txf[m_sm1]); + pio_sm_init(m_pio, m_sm0, m_rdDataOffset, &m_rdDataConfig); + pio_sm_init(m_pio, m_sm1, m_rdClkOffset, &m_rdClkConfig); + pio_set_sm_mask_enabled(m_pio, mask, true); + + uint nf = nr < DAT_FIFO_DEPTH ? nr : DAT_FIFO_DEPTH; + for (uint it = 0; it < nf; it++) { + *txFifo = 0XFF; + } + io_ro_32* rxFifo = reinterpret_cast(&m_pio->rxf[m_sm0]); + uint32_t* dst32 = (uint)dst & 3 ? buf : reinterpret_cast(dst); + uint64_t crc = 0; + uint64_t chk = 0; + Timeout timeout(SD_READ_TIMEOUT); + uint ir = 0; + if (nf < nr) { + uint nb = nr - nf; + while (true) { + while (pio_sm_get_rx_fifo_level(m_pio, m_sm0) < 4) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + uint32_t tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + if (ir == nb) { + break; + } + tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + } + } + for (; ir < nr; ir++) { + while (pio_sm_is_rx_fifo_empty(m_pio, m_sm0)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + uint32_t tmp = *rxFifo; + if (ir < n32) { + dst32[ir] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + } else { + chk <<= 32; + chk |= tmp; + } + } + if (crc != chk) { +#if USE_DEBUG_MODE + Serial.printf("crc: %llX\r\nchk: %llX\r\n", crc, chk); +#endif // USE_DEBUG_MODE + sdError(SD_CARD_ERROR_READ_CRC); + goto fail; + } + pio_set_sm_mask_enabled(m_pio, mask, false); + if (dst32 == buf) { + memcpy(dst, buf, count); + } + return true; + +fail: + pio_set_sm_mask_enabled(m_pio, mask, false); + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readData(uint8_t* dst) { return readData(dst, 512); } +//------------------------------------------------------------------------------ +bool PioSdioCard::readOCR(uint32_t* ocr) { + *ocr = m_ocr; + return true; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readSCR(scr_t* scr) { + memcpy(scr, &m_scr, sizeof(scr_t)); + return true; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readSDS(sds_t* sds) { + memcpy(sds, &m_sds, sizeof(sds_t)); + return true; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readSector(Sector_t sector, uint8_t* dst) { + if (m_curState != READ_STATE || sector != m_curSector) { + if (!syncDevice()) { + SDIO_FAIL(); + goto fail; + } + if (!readStart(sector)) { + sdError(SD_CARD_ERROR_READ_START); + goto fail; + } + m_curSector = sector; + m_curState = READ_STATE; + } + if (!readData(dst, 512)) { + SDIO_FAIL(); + goto fail; + } + m_curSector++; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readSectors(Sector_t sector, uint8_t* dst, size_t ns) { + for (size_t i = 0; i < ns; i++) { + if (!readSector(sector + i, dst + i * 512UL)) { + SDIO_FAIL(); + goto fail; + } + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readStart(Sector_t sector) { + uint arg = m_highCapacity ? sector : 512 * sector; + if (!cardCommand(CMD18_R1, arg)) { + sdError(SD_CARD_ERROR_CMD18); + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::readStop() { + if (!syncDevice()) { + SDIO_FAIL(); + return false; + } + return true; +} +//------------------------------------------------------------------------------ +uint32_t PioSdioCard::status() { + return cardCommand(CMD13_R1, m_rca) ? m_cardRsp : CARD_STATUS_ERROR; +} +//------------------------------------------------------------------------------ +Sector_t PioSdioCard::sectorCount() { return m_csd.capacity(); } +//------------------------------------------------------------------------------ +bool PioSdioCard::syncDevice() { + if (m_curState != IDLE_STATE) { + Timeout timeout(SD_INIT_TIMEOUT); + while (!gpio_get(m_dat0Pin)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_CMD12); + goto fail; + } + } + if (!cardCommand(CMD12_R1, 0)) { + sdError(SD_CARD_ERROR_CMD12); + goto fail; + } + while (isBusy()) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_CMD12); + goto fail; + } + } + m_curState = IDLE_STATE; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint8_t PioSdioCard::type() const { + return !m_initDone ? 0 + : !m_version2 ? SD_CARD_TYPE_SD1 + : !m_highCapacity ? SD_CARD_TYPE_SD2 + : SD_CARD_TYPE_SDHC; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::writeSector(Sector_t sector, const uint8_t* src) { + return writeSectors(sector, src, 1); +} +//------------------------------------------------------------------------------ +bool PioSdioCard::writeSectors(Sector_t sector, const uint8_t* src, size_t ns) { + if (m_curState != WRITE_STATE || m_curSector != sector) { + if (!syncDevice()) { + SDIO_FAIL(); + goto fail; + } + if (!writeStart(sector)) { + sdError(SD_CARD_ERROR_WRITE_START); + goto fail; + } + m_curSector = sector; + m_curState = WRITE_STATE; + } + for (size_t i = 0; i < ns; i++, src += 512) { + if (!writeData(src)) { + sdError(SD_CARD_ERROR_WRITE_DATA); + goto fail; + } + } + m_curSector += ns; + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +bool __time_critical_func(PioSdioCard::writeData)(const uint8_t* src) { + const uint32_t* src32; + uint32_t buf[128]; + if ((uint)src & 3) { + memcpy(buf, src, 512); + src32 = (const uint32_t*)buf; + } else { + src32 = (const uint32_t*)src; + } + uint32_t tmp; + io_wo_32* txFifo = reinterpret_cast(&m_pio->txf[m_sm0]); + io_ro_32* rxFifo = reinterpret_cast(&m_pio->rxf[m_sm1]); + uint8_t rsp; + uint mask = (1ul << m_sm0) | (1ul << m_sm1); + uint64_t crc = 0; + + Timeout timeout(SD_WRITE_TIMEOUT); + while (!gpio_get(m_dat0Pin)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + } + pio_sm_init(m_pio, m_sm0, m_wrDataOffset, &m_wrDataConfig); + pio_sm_init(m_pio, m_sm1, m_wrRespOffset, &m_wrRespConfig); + *txFifo = 1048; // 8 + 1024 + 16 + 1 - 1; + pio_sm_exec(m_pio, m_sm0, pio_encode_out(pio_x, 32)); + pio_sm_exec(m_pio, m_sm0, pio_encode_set(pio_pindirs, 0XF)); + *txFifo = 0xFFFFFFF0; + pio_set_sm_mask_enabled(m_pio, mask, true); + for (int i = 0; i < 128;) { + while (pio_sm_get_tx_fifo_level(m_pio, m_sm0) > 4) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_WRITE_FIFO); + goto fail; + } + } + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + } + while (pio_sm_get_tx_fifo_level(m_pio, m_sm0) > 5) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_WRITE_FIFO); + goto fail; + } + } + *txFifo = static_cast(crc >> 32); + *txFifo = static_cast(crc); + *txFifo = 0xFFFFFFFF; + + while (pio_sm_is_rx_fifo_empty(m_pio, m_sm1)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_FIFO); + goto fail; + } + } + rsp = *rxFifo; + if ((rsp & 0X1F) != 0b101) { +#if USE_DEBUG_MODE + Serial.printf("wr rsp: %02X\n", rsp); +#endif // USE_DEBUG_MODE + sdError(SD_CARD_ERROR_WRITE_DATA); + goto fail; + } + return true; +fail: + pio_set_sm_mask_enabled(m_pio, mask, false); + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::writeStart(Sector_t sector) { + uint arg = m_highCapacity ? sector : 512 * sector; + if (!cardCommand(CMD25_R1, arg)) { + sdError(SD_CARD_ERROR_CMD25); + goto fail; + } + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +bool PioSdioCard::writeStop() { + if (!syncDevice()) { + SDIO_FAIL(); + return false; + } + return true; +} +#endif // ARDUINO_ARCH_RP2040 diff --git a/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.h b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.h new file mode 100644 index 00000000..5f7290b8 --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.h @@ -0,0 +1,336 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Classes for PIO SDIO cards. + */ +#pragma once +#include "../../common/SysCall.h" +#include "../SdCardInterface.h" +#if defined(ARDUINO_ARCH_RP2040) && defined(PIN_SD_CLK) && \ + defined(PIN_SD_CMD_MOSI) && defined(PIN_SD_DAT0_MISO) && \ + defined(PIN_SD_DAT1) && defined(PIN_SD_DAT2) && defined(PIN_SD_DAT3_CS) +#define HAS_BUILTIN_PIO_SDIO +#endif +class PioSdioConfig; +/** SdioConfig type for PIO SDIO */ +typedef PioSdioConfig SdioConfig; +class PioSdioCard; +/** Sdio type for PIO SDIO */ +typedef PioSdioCard SdioCard; +//------------------------------------------------------------------------------ +/** + * \class PioSdioConfig + * \brief SDIO card configuration. + */ +class PioSdioConfig { + public: + /** + * PioSdioConfig constructor. + * \param[in] clkPin gpio pin for SDIO CLK. + * \param[in] cmdPin gpio pin for SDIO CMD. + * \param[in] dat0Pin gpio start pin for SDIO DAT[4]. + * \param[in] clkDiv PIO clock divisor. + */ + PioSdioConfig(uint clkPin, uint cmdPin, uint dat0Pin, float clkDiv = 1.0) + : m_clkPin(clkPin), + m_cmdPin(cmdPin), + m_dat0Pin(dat0Pin), + m_clkDiv(clkDiv) {} + /** \return gpio for SDIO CLK */ + uint clkPin() { return m_clkPin; } + /** \return gpio for SDIO CMD */ + uint cmdPin() { return m_cmdPin; } + /** \return gpio for SDIO DAT0 */ + uint dat0Pin() { return m_dat0Pin; } + /** \return PIO clock divisor */ + float clkDiv() { return m_clkDiv; } + + private: + PioSdioConfig() : m_clkPin(63u), m_cmdPin(63u), m_dat0Pin(63u), m_clkDiv(0) {} + const uint8_t m_clkPin; + const uint8_t m_cmdPin; + const uint8_t m_dat0Pin; + const float m_clkDiv; +}; +//------------------------------------------------------------------------------ +/** + * \class CmdRsp_t + * \brief SD command/response type. + */ +class CmdRsp_t { + public: + /** + * \param[in] idx_ Command index. + * \param[in] rsp_ Response type. + */ + CmdRsp_t(uint8_t idx_, uint8_t rsp_) : idx(idx_), rsp(rsp_) {} + uint8_t idx; ///< Command index. + uint8_t rsp; ///< Response type. +}; +//------------------------------------------------------------------------------ +/** + * \class PioSdioCard + * \brief Raw SDIO access to SD and SDHC flash memory cards. + */ +class PioSdioCard : public SdCardInterface { + public: + PioSdioCard() = default; // cppcheck-suppress uninitMemberVar + /** Initialize the SD card. + * \param[in] config SDIO card configuration. + * \return true for success or false for failure. + */ + bool begin(PioSdioConfig config); + /** CMD6 Switch mode: Check Function Set Function. + * \param[in] arg CMD6 argument. + * \param[out] status return status data. + * + * \return true for success or false for failure. + */ + bool cardCMD6(uint32_t arg, uint8_t* status) final; + /** Disable an SDIO card. + * not implemented. + */ + void end() final; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + uint32_t __attribute__((error("use sectorCount()"))) cardSize(); +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \note This function requests the SD card to do a flash erase for a + * range of sectors. The data on the card after an erase operation is + * either 0 or 1, depends on the card vendor. The card must support + * single sector erase. + * + * \return true for success or false for failure. + */ + bool erase(Sector_t firstSector, Sector_t lastSector) final; + /** + * \return code for the last error. See SdCardInfo.h for a list of error + * codes. + */ + uint8_t errorCode() const final; + /** \return error data for last error. */ + uint32_t errorData() const final; + /** \return error line for last error. Tmp function for debug. */ + uint32_t errorLine() const; + /** + * Check for busy with CMD13. + * + * \return true if busy else false. + */ + bool isBusy() final; + /** \return the SD clock frequency in kHz. */ + uint32_t kHzSdClk(); + /** + * Read a 512 byte sector from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSector(Sector_t sector, uint8_t* dst) final; + /** + * Read multiple 512 byte sectors from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSectors(Sector_t sector, uint8_t* dst, size_t ns) final; + /** + * Read a card's CID register. The CID contains card identification + * information such as Manufacturer ID, Product name, Product serial + * number and Manufacturing date. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCID(cid_t* cid) final; + /** + * Read a card's CSD register. The CSD contains Card-Specific Data that + * provides information regarding access to the card's contents. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCSD(csd_t* csd) final; + /** Read one data sector in a multiple sector read sequence + * + * \param[out] dst Pointer to the location for the data to be read. + * + * \return true for success or false for failure. + */ + bool readData(uint8_t* dst); + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ + bool readOCR(uint32_t* ocr) final; + /** Read SCR register. + * + * \param[out] scr Value of SCR register. + * \return true for success or false for failure. + */ + bool readSCR(scr_t* scr) final; + /** Return the 64 byte SD Status register. + * \param[out] sds location for 64 status bytes. + * \return true for success or false for failure. + */ + bool readSDS(sds_t* sds) final; + /** Start a read multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. + * + * \return true for success or false for failure. + */ + bool readStart(Sector_t sector); + /** End a read multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool readStop(); + /** \return SDIO card status. */ + uint32_t status() final; + /** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data sectors in the card + * or zero if an error occurs. + */ + Sector_t sectorCount() final; + /** + * Send CMD12 to stop read or write. + * + * \param[in] blocking If true, wait for command complete. + * + * \return true for success or false for failure. + */ + bool stopTransmission(bool blocking); + /** \return success if sync successful. Not for user apps. */ + bool syncDevice() final; + /** Return the card type: SD V1, SD V2 or SDHC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC. + */ + uint8_t type() const final; + /** + * Writes a 512 byte sector to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSector(Sector_t sector, const uint8_t* src) final; + /** + * Write multiple 512 byte sectors to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSectors(Sector_t sector, const uint8_t* src, size_t ns) final; + /** Write one data sector in a multiple sector write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeData(const uint8_t* src); + /** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ + bool writeStart(Sector_t sector); + + /** End a write multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool writeStop(); + + private: + //---------------------------------------------------------------------------- + bool cardAcmd(uint32_t rca, CmdRsp_t cmdRsp, uint32_t arg); + bool cardCommand(CmdRsp_t cmd, uint32_t arg, void* rsp = nullptr); + void pioConfig(float clkDiv); + void pioEnd(); + bool pioInit(); + void powerUpClockCycles(); + bool readData(void* dst, size_t count); + void setSdErrorCode(uint8_t code, uint32_t line) { + m_errorCode = code; + m_errorLine = line; + } + static const uint8_t IDLE_STATE = 0; + static const uint8_t READ_STATE = 1; + static const uint8_t WRITE_STATE = 2; + Sector_t m_curSector = 0; + uint8_t m_curState = IDLE_STATE; + uint m_cardRsp; + uint m_errorCode; + uint m_errorLine; + bool m_highCapacity; + bool m_initDone = false; + uint m_ocr; + bool m_version2; + uint m_rca; + cid_t m_cid; + csd_t m_csd; + scr_t m_scr; + sds_t m_sds; + + float m_clkDiv = 0; + uint m_clkPin = 63u; // PIN_SDIO_UNDEFINED; + uint m_cmdPin = 63u; // PIN_SDIO_UNDEFINED; + uint m_dat0Pin = 63u; // PIN_SDIO_UNDEFINED; + PIO m_pio = nullptr; + int m_sm0 = -1; + int m_sm1 = -1; + int m_cmdRspOffset = -1; + pio_sm_config m_cmdConfig; + int m_rdDataOffset = -1; + pio_sm_config m_rdDataConfig; + int m_rdClkOffset = -1; + pio_sm_config m_rdClkConfig; + int m_wrDataOffset = -1; + pio_sm_config m_wrDataConfig; + int m_wrRespOffset = -1; + pio_sm_config m_wrRespConfig; +}; diff --git a/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.pio b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.pio new file mode 100644 index 00000000..481800c2 --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.pio @@ -0,0 +1,187 @@ +; Copyright (c) 2011-2025 Bill Greiman +; This file is part of the SdFat library for SD memory cards. +; +; MIT License +; +; Permission is hereby granted, free of charge, to any person obtaining a +; copy of this software and associated documentation files (the "Software"), +; to deal in the Software without restriction, including without limitation +; the rights to use, copy, modify, merge, publish, distribute, sublicense, +; and/or sell copies of the Software, and to permit persons to whom the +; Software is furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included +; in all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +; OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +; DEALINGS IN THE SOFTWARE. +; + +.define public SDIO_IRQ 7 + +.program cmd_rsp +.side_set 1 opt +.wrap_target +cmd_begin: +send_cmd: + out pins, 1 side 0 [1] + jmp X-- send_cmd side 1 [1] + + jmp !Y cmd_begin side 0 [1] + set pindirs, 0 side 1 [3] +wait_resp: + nop side 0 [3] + nop side 1 [2] + jmp PIN wait_resp + +read_resp: + in pins, 1 + push iffull block side 0 [2] + jmp Y-- read_resp side 1 [1] +.wrap + +% c-sdk { +static inline pio_sm_config pio_cmd_rsp_program_config(uint offset, uint cmd_pin, uint clk_pin, float clk_div) { + pio_sm_config c = cmd_rsp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, cmd_pin, 1); + sm_config_set_in_pins(&c, cmd_pin); + sm_config_set_set_pins(&c, cmd_pin, 1); + sm_config_set_jmp_pin(&c, cmd_pin); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} + +.program rd_clk +.side_set 1 opt +wait_d0: + nop side 0 [3] + jmp PIN wait_d0 side 1 [3] + + irq SDIO_IRQ +.wrap_target + out null, 1 side 0 [2] ; Clock stops when txFifo is empty + nop side 1 [1] +.wrap + +% c-sdk { +static inline pio_sm_config pio_rd_clk_program_config(uint offset, uint d0_pin, uint clk_pin, float clk_div) { + pio_sm_config c = rd_clk_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, d0_pin); + sm_config_set_jmp_pin(&c, d0_pin); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} + +.program rd_data + wait 1 irq SDIO_IRQ +.wrap_target +public wait0: + wait 0 gpio 0 ; See rd_data_patch_program for CLK pin +public wait1: + wait 1 gpio 0 ; See rd_data_patch_program for CLK pin + in pins, 4 +.wrap + +% c-sdk { +static inline void rd_data_patch_program(pio_program *prog, uint16_t* inst, uint clk_pin) { + *prog = rd_data_program; + prog->instructions = inst; +#if PICO_PIO_VERSION > 0 + prog->used_gpio_ranges = clk_pin < 16 ? 1 : clk_pin < 32 ? 2 : clk_pin < 48 ? 4 : 8; +#endif + memcpy(inst, rd_data_program_instructions, sizeof(rd_data_program_instructions)); + inst[rd_data_offset_wait0] = pio_encode_wait_gpio(0, clk_pin); + inst[rd_data_offset_wait1] = pio_encode_wait_gpio(1, clk_pin); +} + +static inline pio_sm_config pio_rd_data_program_config(uint offset, uint data_pin, float clk_div) { + pio_sm_config c = rd_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} + +; Data transmission program +; +; Before running this program, pindirs should be set as output +; and register X should be initialized with the number of nibbles +; to send minus 1 (typically 8 + 1024 + 16 + 1 - 1 = 1048) +; +; Words written to TX FIFO must be: +; - Word 0: start token 0xFFFFFFF0 +; - Word 1-128: transmitted data (512 bytes) +; - Word 129-130: CRC checksum +; - Word 131: end token 0xFFFFFFFF +.program wr_data +.side_set 1 opt +; out X, 32 +; set pindirs, 0XF +tx_loop: + out pins, 4 side 0 [1] + jmp X-- tx_loop side 1 [1] + irq SDIO_IRQ +.wrap_target + nop +.wrap + +% c-sdk { +static inline pio_sm_config pio_wr_data_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, data_pin, 4); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_out_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} +.program wr_resp +.side_set 1 opt + wait 1 irq SDIO_IRQ + set pindirs, 0 [1] +.wrap_target + in pins, 1 side 1 [4] + push iffull noblock side 0 [4] +.wrap + +% c-sdk { +static inline pio_sm_config pio_wr_resp_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_resp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +static const size_t total_pio_length = + cmd_rsp_program.length + rd_data_program.length + rd_clk_program.length + + wr_data_program.length + wr_resp_program.length; +%} + +.program fill_pio +tag0: + jmp tag0 +tag1: + jmp tag1 +tag2: + jmp tag2 +tag3: + jmp tag3 \ No newline at end of file diff --git a/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.pio.h b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.pio.h new file mode 100644 index 00000000..8c6c7f0b --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/PioSdioCard.pio.h @@ -0,0 +1,307 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +#pragma once + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +#define SDIO_IRQ 7 + +// ------- // +// cmd_rsp // +// ------- // + +#define cmd_rsp_wrap_target 0 +#define cmd_rsp_wrap 9 +#define cmd_rsp_pio_version 0 + +static const uint16_t cmd_rsp_program_instructions[] = { + // .wrap_target + 0x7101, // 0: out pins, 1 side 0 [1] + 0x1940, // 1: jmp x--, 0 side 1 [1] + 0x1160, // 2: jmp !y, 0 side 0 [1] + 0xfb80, // 3: set pindirs, 0 side 1 [3] + 0xb342, // 4: nop side 0 [3] + 0xba42, // 5: nop side 1 [2] + 0x00c4, // 6: jmp pin, 4 + 0x4001, // 7: in pins, 1 + 0x9260, // 8: push iffull block side 0 [2] + 0x1987, // 9: jmp y--, 7 side 1 [1] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program cmd_rsp_program = { + .instructions = cmd_rsp_program_instructions, + .length = 10, + .origin = -1, + .pio_version = cmd_rsp_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config cmd_rsp_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + cmd_rsp_wrap_target, offset + cmd_rsp_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_cmd_rsp_program_config(uint offset, uint cmd_pin, uint clk_pin, float clk_div) { + pio_sm_config c = cmd_rsp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, cmd_pin, 1); + sm_config_set_in_pins(&c, cmd_pin); + sm_config_set_set_pins(&c, cmd_pin, 1); + sm_config_set_jmp_pin(&c, cmd_pin); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------ // +// rd_clk // +// ------ // + +#define rd_clk_wrap_target 3 +#define rd_clk_wrap 4 +#define rd_clk_pio_version 0 + +static const uint16_t rd_clk_program_instructions[] = { + 0xb342, // 0: nop side 0 [3] + 0x1bc0, // 1: jmp pin, 0 side 1 [3] + 0xc007, // 2: irq nowait 7 + // .wrap_target + 0x7261, // 3: out null, 1 side 0 [2] + 0xb942, // 4: nop side 1 [1] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program rd_clk_program = { + .instructions = rd_clk_program_instructions, + .length = 5, + .origin = -1, + .pio_version = rd_clk_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config rd_clk_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + rd_clk_wrap_target, offset + rd_clk_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_rd_clk_program_config(uint offset, uint d0_pin, uint clk_pin, float clk_div) { + pio_sm_config c = rd_clk_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, d0_pin); + sm_config_set_jmp_pin(&c, d0_pin); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------- // +// rd_data // +// ------- // + +#define rd_data_wrap_target 1 +#define rd_data_wrap 3 +#define rd_data_pio_version 0 + +#define rd_data_offset_wait0 1u +#define rd_data_offset_wait1 2u + +static const uint16_t rd_data_program_instructions[] = { + 0x20c7, // 0: wait 1 irq, 7 + // .wrap_target + 0x2000, // 1: wait 0 gpio, 0 + 0x2080, // 2: wait 1 gpio, 0 + 0x4004, // 3: in pins, 4 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program rd_data_program = { + .instructions = rd_data_program_instructions, + .length = 4, + .origin = -1, + .pio_version = rd_data_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x1 +#endif +}; + +static inline pio_sm_config rd_data_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + rd_data_wrap_target, offset + rd_data_wrap); + return c; +} + +static inline void rd_data_patch_program(pio_program *prog, uint16_t* inst, uint clk_pin) { + *prog = rd_data_program; + prog->instructions = inst; +#if PICO_PIO_VERSION > 0 + prog->used_gpio_ranges = clk_pin < 16 ? 1 : clk_pin < 32 ? 2 : clk_pin < 48 ? 4 : 8; +#endif + memcpy(inst, rd_data_program_instructions, sizeof(rd_data_program_instructions)); + inst[rd_data_offset_wait0] = pio_encode_wait_gpio(0, clk_pin); + inst[rd_data_offset_wait1] = pio_encode_wait_gpio(1, clk_pin); +} +static inline pio_sm_config pio_rd_data_program_config(uint offset, uint data_pin, float clk_div) { + pio_sm_config c = rd_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------- // +// wr_data // +// ------- // + +#define wr_data_wrap_target 3 +#define wr_data_wrap 3 +#define wr_data_pio_version 0 + +static const uint16_t wr_data_program_instructions[] = { + 0x7104, // 0: out pins, 4 side 0 [1] + 0x1940, // 1: jmp x--, 0 side 1 [1] + 0xc007, // 2: irq nowait 7 + // .wrap_target + 0xa042, // 3: nop + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program wr_data_program = { + .instructions = wr_data_program_instructions, + .length = 4, + .origin = -1, + .pio_version = wr_data_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config wr_data_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + wr_data_wrap_target, offset + wr_data_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_wr_data_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, data_pin, 4); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_out_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------- // +// wr_resp // +// ------- // + +#define wr_resp_wrap_target 2 +#define wr_resp_wrap 3 +#define wr_resp_pio_version 0 + +static const uint16_t wr_resp_program_instructions[] = { + 0x20c7, // 0: wait 1 irq, 7 + 0xe180, // 1: set pindirs, 0 [1] + // .wrap_target + 0x5c01, // 2: in pins, 1 side 1 [4] + 0x9440, // 3: push iffull noblock side 0 [4] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program wr_resp_program = { + .instructions = wr_resp_program_instructions, + .length = 4, + .origin = -1, + .pio_version = wr_resp_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config wr_resp_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + wr_resp_wrap_target, offset + wr_resp_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_wr_resp_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_resp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +static const size_t total_pio_length = + cmd_rsp_program.length + rd_data_program.length + rd_clk_program.length + + wr_data_program.length + wr_resp_program.length; + +#endif + +// -------- // +// fill_pio // +// -------- // + +#define fill_pio_wrap_target 0 +#define fill_pio_wrap 3 +#define fill_pio_pio_version 0 + +static const uint16_t fill_pio_program_instructions[] = { + // .wrap_target + 0x0000, // 0: jmp 0 + 0x0001, // 1: jmp 1 + 0x0002, // 2: jmp 2 + 0x0003, // 3: jmp 3 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program fill_pio_program = { + .instructions = fill_pio_program_instructions, + .length = 4, + .origin = -1, + .pio_version = fill_pio_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config fill_pio_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + fill_pio_wrap_target, offset + fill_pio_wrap); + return c; +} +#endif diff --git a/third_party/sdfat/src/SdCard/PioSdio/pioasm.bat b/third_party/sdfat/src/SdCard/PioSdio/pioasm.bat new file mode 100644 index 00000000..75207317 --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/pioasm.bat @@ -0,0 +1,3 @@ +pause assemble PioSdioCard.pio +..\..\..\extras\pioasm\pioasm -p 0 PioSdioCard.pio PioSdioCard.pio.h +pause done \ No newline at end of file diff --git a/third_party/sdfat/src/SdCard/PioSdio/pioasm_test.bat b/third_party/sdfat/src/SdCard/PioSdio/pioasm_test.bat new file mode 100644 index 00000000..e3d42849 --- /dev/null +++ b/third_party/sdfat/src/SdCard/PioSdio/pioasm_test.bat @@ -0,0 +1,3 @@ +pause assemble PioSdioCard.pio +..\..\..\extras\pioasm\pioasm -v 1 PioSdioCard.pio PioSdioCard_test.h +pause done \ No newline at end of file diff --git a/third_party/sdfat/src/SdCard/SdCard.h b/third_party/sdfat/src/SdCard/SdCard.h new file mode 100644 index 00000000..932c8adc --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdCard.h @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Top level include for SPI and SDIO cards. + */ +#pragma once +#include "SdSpiCard/SdSpiCard.h" +#if defined(HAS_PIO_SDIO) +#include "PioSdio/PioSdioCard.h" +#elif defined(HAS_TEENSY_SDIO) +#include "TeensySdio/TeensySdioCard.h" +#else +class SdioConfig {}; +#endif // + +#if HAS_SDIO_CLASS +/** Type for both SPI and SDIO cards. */ +typedef SdCardInterface SdCard; +#else // HAS_SDIO_CLASS +/** Type for SPI card. */ +typedef SdSpiCard SdCard; +#endif // HAS_SDIO_CLASS +/** Determine card configuration type. + * + * \param[in] cfg Card configuration. + * \return true if SPI. + */ +inline bool isSpi(SdSpiConfig cfg) { + (void)cfg; + return true; +} +/** Determine card configuration type. + * + * \param[in] cfg Card configuration. + * \return true if SPI. + */ +inline bool isSpi(SdioConfig cfg) { + (void)cfg; + return false; +} +/** + * \class SdCardFactory + * \brief Setup a SPI card or SDIO card. + */ +class SdCardFactory { + public: + /** Initialize SPI card. + * + * \param[in] config SPI configuration. + * \return generic card pointer or nullptr if failure. + */ + SdCard* newCard(SdSpiConfig config) { + m_spiCard.begin(config); + return &m_spiCard; + } + /** Initialize SDIO card. + * + * \param[in] config SDIO configuration. + * \return generic card pointer or nullptr if SDIO is not supported. + */ + SdCard* newCard(SdioConfig config) { +#if HAS_SDIO_CLASS + m_sdioCard.begin(config); + return &m_sdioCard; +#else // HAS_SDIO_CLASS + (void)config; + return nullptr; +#endif // HAS_SDIO_CLASS + } + + private: +#if HAS_SDIO_CLASS + SdioCard m_sdioCard; +#endif // HAS_SDIO_CLASS + SdSpiCard m_spiCard; +}; diff --git a/third_party/sdfat/src/SdCard/SdCardInfo.cpp b/third_party/sdfat/src/SdCard/SdCardInfo.cpp new file mode 100644 index 00000000..e89011bf --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdCardInfo.cpp @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdCardInfo.h" +//------------------------------------------------------------------------------ +#undef SD_CARD_ERROR +#define SD_CARD_ERROR(e, m) \ + case SD_CARD_ERROR_##e: \ + pr->print(F(#e)); \ + break; +void printSdErrorSymbol(print_t* pr, uint8_t code) { + pr->print(F("SD_CARD_ERROR_")); + switch (code) { + SD_ERROR_CODE_LIST + default: + pr->print(F("UNKNOWN")); + } +} +//------------------------------------------------------------------------------ +#undef SD_CARD_ERROR +#define SD_CARD_ERROR(e, m) \ + case SD_CARD_ERROR_##e: \ + pr->print(F(m)); \ + break; +void printSdErrorText(print_t* pr, uint8_t code) { + switch (code) { + SD_ERROR_CODE_LIST + default: + pr->print(F("Unknown error")); + } +} diff --git a/third_party/sdfat/src/SdCard/SdCardInfo.h b/third_party/sdfat/src/SdCard/SdCardInfo.h new file mode 100644 index 00000000..5c3d52a4 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdCardInfo.h @@ -0,0 +1,478 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Definitions for SD cards. + */ +#pragma once +#include + +#include "../common/SysCall.h" +// Based on the document: +// +// SD Specifications +// Part 1 +// Physical Layer +// Simplified Specification +// Version 8.00 +// Sep 23, 2020 +// +// https://www.sdcard.org/downloads/pls/ +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +// SD registers are big endian. +#error bit fields in structures assume little endian processor. +#endif // __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +//------------------------------------------------------------------------------ +// SD card errors +// See the SD Specification for command info. +/** Define error codes and brief description. */ +#define SD_ERROR_CODE_LIST \ + SD_CARD_ERROR(NONE, "No error") \ + SD_CARD_ERROR(CMD0, "Card reset failed") \ + SD_CARD_ERROR(CMD2, "SDIO read CID") \ + SD_CARD_ERROR(CMD3, "SDIO publish RCA") \ + SD_CARD_ERROR(CMD6, "Switch card function") \ + SD_CARD_ERROR(CMD7, "SDIO card select") \ + SD_CARD_ERROR(CMD8, "Send and check interface settings") \ + SD_CARD_ERROR(CMD9, "Read CSD data") \ + SD_CARD_ERROR(CMD10, "Read CID data") \ + SD_CARD_ERROR(CMD12, "Stop multiple block transmission") \ + SD_CARD_ERROR(CMD13, "Read card status") \ + SD_CARD_ERROR(CMD17, "Read single block") \ + SD_CARD_ERROR(CMD18, "Read multiple blocks") \ + SD_CARD_ERROR(CMD24, "Write single block") \ + SD_CARD_ERROR(CMD25, "Write multiple blocks") \ + SD_CARD_ERROR(CMD32, "Set first erase block") \ + SD_CARD_ERROR(CMD33, "Set last erase block") \ + SD_CARD_ERROR(CMD38, "Erase selected blocks") \ + SD_CARD_ERROR(CMD58, "Read OCR register") \ + SD_CARD_ERROR(CMD59, "Set CRC mode") \ + SD_CARD_ERROR(ACMD6, "Set SDIO bus width") \ + SD_CARD_ERROR(ACMD13, "Read extended status") \ + SD_CARD_ERROR(ACMD23, "Set pre-erased count") \ + SD_CARD_ERROR(ACMD41, "Activate card initialization") \ + SD_CARD_ERROR(ACMD51, "Read SCR data") \ + SD_CARD_ERROR(READ_TOKEN, "Bad read data token") \ + SD_CARD_ERROR(READ_CRC, "Read CRC error") \ + SD_CARD_ERROR(READ_FIFO, "SDIO fifo read timeout") \ + SD_CARD_ERROR(READ_REG, "Read CID or CSD failed.") \ + SD_CARD_ERROR(READ_START, "Bad readStart argument") \ + SD_CARD_ERROR(READ_TIMEOUT, "Read data timeout") \ + SD_CARD_ERROR(STOP_TRAN, "Multiple block stop failed") \ + SD_CARD_ERROR(TRANSFER_COMPLETE, "SDIO transfer complete") \ + SD_CARD_ERROR(WRITE_DATA, "Write data not accepted") \ + SD_CARD_ERROR(WRITE_FIFO, "SDIO fifo write timeout") \ + SD_CARD_ERROR(WRITE_START, "Bad writeStart argument") \ + SD_CARD_ERROR(WRITE_PROGRAMMING, "Flash programming") \ + SD_CARD_ERROR(WRITE_TIMEOUT, "Write timeout") \ + SD_CARD_ERROR(DMA, "DMA transfer failed") \ + SD_CARD_ERROR(ERASE, "Card did not accept erase commands") \ + SD_CARD_ERROR(ERASE_SINGLE_SECTOR, "Card does not support erase") \ + SD_CARD_ERROR(ERASE_TIMEOUT, "Erase command timeout") \ + SD_CARD_ERROR(INIT_NOT_CALLED, "Card has not been initialized") \ + SD_CARD_ERROR(ADD_PIO_PROGRAM, "Add PIO program") \ + SD_CARD_ERROR(INVALID_CARD_CONFIG, "Invalid card config") \ + SD_CARD_ERROR(FUNCTION_NOT_SUPPORTED, "Unsupported SDIO command") + +enum { +/** Macro for generation of error codes using an enum. */ +#define SD_CARD_ERROR(e, m) SD_CARD_ERROR_##e, + SD_ERROR_CODE_LIST +#undef SD_CARD_ERROR + SD_CARD_ERROR_UNKNOWN +}; +/** Print the enum symbol for an error code. + * \param[in] pr Print stream. + * \param[in] code enum value for error. + */ +void printSdErrorSymbol(print_t* pr, uint8_t code); +/** Print text for an error code. + * \param[in] pr Print stream. + * \param[in] code enum value for error. + */ +void printSdErrorText(print_t* pr, uint8_t code); +//------------------------------------------------------------------------------ +// card types +/** Standard capacity V1 SD card */ +const uint8_t SD_CARD_TYPE_SD1 = 1; +/** Standard capacity V2 SD card */ +const uint8_t SD_CARD_TYPE_SD2 = 2; +/** High Capacity SD card */ +const uint8_t SD_CARD_TYPE_SDHC = 3; +//------------------------------------------------------------------------------ +// SD operation timeouts +/** command timeout ms */ +const uint16_t SD_CMD_TIMEOUT = 300; +/** erase timeout ms */ +const uint16_t SD_ERASE_TIMEOUT = 10000; +/** init timeout ms */ +const uint16_t SD_INIT_TIMEOUT = 2000; +/** read timeout ms */ +const uint16_t SD_READ_TIMEOUT = 300; +/** write time out ms */ +const uint16_t SD_WRITE_TIMEOUT = 600; +//------------------------------------------------------------------------------ +// SD card commands +/** GO_IDLE_STATE - init card in spi mode if CS low */ +const uint8_t CMD0 = 0X00; +/** ALL_SEND_CID - Asks any card to send the CID. */ +const uint8_t CMD2 = 0X02; +/** SEND_RELATIVE_ADDR - Ask the card to publish a new RCA. */ +const uint8_t CMD3 = 0X03; +/** SWITCH_FUNC - Switch Function Command */ +const uint8_t CMD6 = 0X06; +/** SELECT/DESELECT_CARD - toggles between the stand-by and transfer states. */ +const uint8_t CMD7 = 0X07; +/** SEND_IF_COND - verify SD Memory Card interface operating condition.*/ +const uint8_t CMD8 = 0X08; +/** SEND_CSD - read the Card Specific Data (CSD register) */ +const uint8_t CMD9 = 0X09; +/** SEND_CID - read the card identification information (CID register) */ +const uint8_t CMD10 = 0X0A; +/** VOLTAGE_SWITCH -Switch to 1.8V bus signaling level. */ +const uint8_t CMD11 = 0X0B; +/** STOP_TRANSMISSION - end multiple sector read sequence */ +const uint8_t CMD12 = 0X0C; +/** SEND_STATUS - read the card status register */ +const uint8_t CMD13 = 0X0D; +/** READ_SINGLE_SECTOR - read a single data sector from the card */ +const uint8_t CMD17 = 0X11; +/** READ_MULTIPLE_SECTOR - read multiple data sectors from the card */ +const uint8_t CMD18 = 0X12; +/** WRITE_SECTOR - write a single data sector to the card */ +const uint8_t CMD24 = 0X18; +/** WRITE_MULTIPLE_SECTOR - write sectors of data until a STOP_TRANSMISSION */ +const uint8_t CMD25 = 0X19; +/** ERASE_WR_BLK_START - sets the address of the first sector to be erased */ +const uint8_t CMD32 = 0X20; +/** ERASE_WR_BLK_END - sets the address of the last sector of the continuous + range to be erased*/ +const uint8_t CMD33 = 0X21; +/** ERASE - erase all previously selected sectors */ +const uint8_t CMD38 = 0X26; +/** APP_CMD - escape for application specific command */ +const uint8_t CMD55 = 0X37; +/** READ_OCR - read the OCR register of a card */ +const uint8_t CMD58 = 0X3A; +/** CRC_ON_OFF - enable or disable CRC checking */ +const uint8_t CMD59 = 0X3B; +/** SET_BUS_WIDTH - Defines the data bus width for data transfer. */ +const uint8_t ACMD6 = 0X06; +/** SD_STATUS - Send the SD Status. */ +const uint8_t ACMD13 = 0X0D; +/** SET_WR_BLK_ERASE_COUNT - Set the number of write sectors to be + pre-erased before writing */ +const uint8_t ACMD23 = 0X17; +/** SD_SEND_OP_COMD - Sends host capacity support information and + activates the card's initialization process */ +const uint8_t ACMD41 = 0X29; +/** Reads the SD Configuration Register (SCR). */ +const uint8_t ACMD51 = 0X33; +//============================================================================== +// CARD_STATUS +/** The command's argument was out of the allowed range for this card. */ +const uint32_t CARD_STATUS_OUT_OF_RANGE = 1UL << 31; +/** A misaligned address which did not match the sector length. */ +const uint32_t CARD_STATUS_ADDRESS_ERROR = 1UL << 30; +/** The transferred sector length is not allowed for this card. */ +const uint32_t CARD_STATUS_SECTOR_LEN_ERROR = 1UL << 29; +/** An error in the sequence of erase commands occurred. */ +const uint32_t CARD_STATUS_ERASE_SEQ_ERROR = 1UL << 28; +/** An invalid selection of write-sectors for erase occurred. */ +const uint32_t CARD_STATUS_ERASE_PARAM = 1UL << 27; +/** Set when the host attempts to write to a protected sector. */ +const uint32_t CARD_STATUS_WP_VIOLATION = 1UL << 26; +/** When set, signals that the card is locked by the host. */ +const uint32_t CARD_STATUS_CARD_IS_LOCKED = 1UL << 25; +/** Set when a sequence or password error has been detected. */ +const uint32_t CARD_STATUS_LOCK_UNLOCK_FAILED = 1UL << 24; +/** The CRC check of the previous command failed. */ +const uint32_t CARD_STATUS_COM_CRC_ERROR = 1UL << 23; +/** Command not legal for the card state. */ +const uint32_t CARD_STATUS_ILLEGAL_COMMAND = 1UL << 22; +/** Card internal ECC was applied but failed to correct the data. */ +const uint32_t CARD_STATUS_CARD_ECC_FAILED = 1UL << 21; +/** Internal card controller error */ +const uint32_t CARD_STATUS_CC_ERROR = 1UL << 20; +/** A general or an unknown error occurred during the operation. */ +const uint32_t CARD_STATUS_ERROR = 1UL << 19; +// bits 19, 18, and 17 reserved. +/** Permanent WP set or attempt to change read only values of CSD. */ +const uint32_t CARD_STATUS_CSD_OVERWRITE = 1UL << 16; +/** partial address space was erased due to write protect. */ +const uint32_t CARD_STATUS_WP_ERASE_SKIP = 1UL << 15; +/** The command has been executed without using the internal ECC. */ +const uint32_t CARD_STATUS_CARD_ECC_DISABLED = 1UL << 14; +/** out of erase sequence command was received. */ +const uint32_t CARD_STATUS_ERASE_RESET = 1UL << 13; +/** The state of the card when receiving the command. + * 0 = idle + * 1 = ready + * 2 = ident + * 3 = stby + * 4 = tran + * 5 = data + * 6 = rcv + * 7 = prg + * 8 = dis + * 9-14 = reserved + * 15 = reserved for I/O mode + */ +const uint32_t CARD_STATUS_CURRENT_STATE = 0XF << 9; +/** Shift for current state. */ +const uint32_t CARD_STATUS_CURRENT_STATE_SHIFT = 9; +/** Corresponds to buffer empty signaling on the bus. */ +const uint32_t CARD_STATUS_READY_FOR_DATA = 1UL << 8; +// bit 7 reserved. +/** Extension Functions may set this bit to get host to deal with events. */ +const uint32_t CARD_STATUS_FX_EVENT = 1UL << 6; +/** The card will expect ACMD, or the command has been interpreted as ACMD */ +const uint32_t CARD_STATUS_APP_CMD = 1UL << 5; +// bit 4 reserved. +/** Error in the sequence of the authentication process. */ +const uint32_t CARD_STATUS_AKE_SEQ_ERROR = 1UL << 3; +// bits 2,1, and 0 reserved for manufacturer test mode. +//============================================================================== +/** status for card in the ready state */ +const uint8_t R1_READY_STATE = 0X00; +/** status for card in the idle state */ +const uint8_t R1_IDLE_STATE = 0X01; +/** status bit for illegal command */ +const uint8_t R1_ILLEGAL_COMMAND = 0X04; +/** start data token for read or write single sector*/ +const uint8_t DATA_START_SECTOR = 0XFE; +/** stop token for write multiple sectors*/ +const uint8_t STOP_TRAN_TOKEN = 0XFD; +/** start data token for write multiple sectors*/ +const uint8_t WRITE_MULTIPLE_TOKEN = 0XFC; +/** mask for data response tokens after a write sector operation */ +const uint8_t DATA_RES_MASK = 0X1F; +/** write data accepted token */ +const uint8_t DATA_RES_ACCEPTED = 0X05; +//============================================================================== +/** + * \class cid_t + * \brief Card Identification (CID) register. + */ +struct cid_t { + // byte 0 + /** Manufacturer ID */ + uint8_t mid; + // byte 1-2 + /** OEM/Application ID. */ + char oid[2]; + // byte 3-7 + /** Product name. */ + char pnm[5]; + // byte 8 + /** Product revision - n.m two 4-bit nibbles. */ + uint8_t prv; + // byte 9-12 + /** Product serial 32-bit number Big Endian format. */ + uint8_t psn8[4]; + // byte 13-14 + /** Manufacturing date big endian - four nibbles RYYM Reserved Year Month. */ + uint8_t mdt[2]; + // byte 15 + /** CRC7 bits 1-7 checksum, bit 0 always 1 */ + uint8_t crc; + // Extract big endian fields. + /** \return major revision number. */ + int prvN() const { return prv >> 4; } + /** \return minor revision number. */ + int prvM() const { return prv & 0XF; } + /** \return Manufacturing Year. */ + int mdtYear() const { return 2000 + ((mdt[0] & 0XF) << 4) + (mdt[1] >> 4); } + /** \return Manufacturing Month. */ + int mdtMonth() const { return mdt[1] & 0XF; } + /** \return Product Serial Number. */ + uint32_t psn() const { + return static_cast(psn8[0]) << 24 | + static_cast(psn8[1]) << 16 | + static_cast(psn8[2]) << 8 | static_cast(psn8[3]); + } +} __attribute__((packed)); +//============================================================================== +/** + * \class csd_t + * \brief Union of old and new style CSD register. + */ +struct csd_t { + /** union of all CSD versions */ + uint8_t csd[16]; + // Extract big endian fields. + /** \return Capacity in sectors */ + uint32_t capacity() const { + uint32_t c_size; + uint8_t ver = csd[0] >> 6; + if (ver == 0) { + c_size = static_cast(csd[6] & 3) << 10; + c_size |= static_cast(csd[7]) << 2 | csd[8] >> 6; + uint8_t c_size_mult = (csd[9] & 3) << 1 | csd[10] >> 7; + uint8_t read_bl_len = csd[5] & 15; + return (c_size + 1) << (c_size_mult + read_bl_len + 2 - 9); + } else if (ver == 1) { + c_size = static_cast(csd[7] & 63) << 16; + c_size |= static_cast(csd[8]) << 8; + c_size |= csd[9]; + return (c_size + 1) << 10; + } else { + return 0; + } + } + /** \return true if erase granularity is single block. */ + bool eraseSingleBlock() const { return csd[10] & 0X40; } + /** \return erase size in 512 byte blocks if eraseSingleBlock is false. */ + int eraseSize() const { return ((csd[10] & 0X3F) << 1 | csd[11] >> 7) + 1; } + /** \return true if the contents is copied or true if original. */ + bool copy() const { return csd[14] & 0X40; } + /** \return true if the entire card is permanently write protected. */ + bool permWriteProtect() const { return csd[14] & 0X20; } + /** \return true if the entire card is temporarily write protected. */ + bool tempWriteProtect() const { return csd[14] & 0X10; } +}; +//============================================================================== +/** + * \class scr_t + * \brief SCR register. + */ +struct scr_t { + /** Bytes 0-3 SD Association, bytes 4-7 reserved for manufacturer. */ + uint8_t scr[8]; + /** \return SCR_STRUCTURE field - must be zero.*/ + uint8_t srcStructure() const { return scr[0] >> 4; } + /** \return SD_SPEC field 0 - v1.0 or V1.01, 1 - 1.10, 2 - V2.00 or greater */ + uint8_t sdSpec() const { return scr[0] & 0XF; } + /** \return false if all zero, true if all one. */ + bool dataAfterErase() const { return scr[1] & 0X80; } + /** \return CPRM Security Version. */ + uint8_t sdSecurity() const { return (scr[1] >> 4) & 0X7; } + /** \return 0101b. */ + uint8_t sdBusWidths() const { return scr[1] & 0XF; } + /** \return true if V3.0 or greater. */ + bool sdSpec3() const { return scr[2] & 0X80; } + /** \return if true and sdSpecX is zero V4.xx. */ + bool sdSpec4() const { return scr[2] & 0X4; } + /** \return nonzero for version 5 or greater if sdSpec == 2, + sdSpec3 == true. Version is return plus four.*/ + uint8_t sdSpecX() const { return (scr[2] & 0X3) << 2 | scr[3] >> 6; } + /** \return bit map for support CMD58/59, CMD48/49, CMD23, and CMD20 */ + uint8_t cmdSupport() const { return scr[3] & 0XF; } + /** \return SD spec version */ + int16_t sdSpecVer() const { + if (sdSpec() > 2) { + return -1; + } else if (sdSpec() < 2) { + return sdSpec() ? 110 : 101; + } else if (!sdSpec3()) { + return 200; + } else if (!sdSpec4() && !sdSpecX()) { + return 300; + } + return 400 + 100 * sdSpecX(); + } +}; +//============================================================================== +/** + * \class sds_t + * \brief SD Status. + */ +// fields are big endian +struct sds_t { + /** byte 0, bit 7-6 width, bit 5 secured mode, bits 4-0 reserved. */ + uint8_t busWidthSecureMode; + /** byte 1 reserved */ + uint8_t reserved1; + /** byte 2-3 zero for SD rd/wr memory card. */ + uint8_t sdCardType[2]; + /** byte 4-7 size of protected area big endian */ + uint8_t sizeOfProtectedArea[4]; + /** byte 8 speed class. */ + uint8_t speed; + /** byte 9 performance move */ + uint8_t performanceMove; + /** byte 10 AU size code. */ + uint8_t auSize; + /** byte 11-12 erase size big endian */ + uint8_t eraseSize[2]; + /** byte 13 erase timeout and erase offset */ + uint8_t eraseTimeoutOffset; + /** byte 14 */ + uint8_t uhsClassAuSize; + /** byte 15 */ + uint8_t videoSpeedClass; + /** byte 16-17 */ + uint8_t vscAuSize[2]; + /** byte 18-21 */ + uint8_t susAddr[3]; + /** byte 21 */ + uint8_t appPerfClass; + /** byte 22 */ + uint8_t perfEnhance; + /** byte 23 */ + uint8_t discardFule; + /** byte 24 */ + uint8_t reservedManufacturer[40]; + + /** \return appClass. */ + int appClass() { return appPerfClass; } + /** \return AU size in KB. or zero for error. */ + uint32_t auSizeKB() { + // 0XF mask and uint16_t array helps compiler optimize size on Uno. + uint8_t val = (auSize >> 4) & 0XF; + static const uint16_t au[] = {0, 16, 32, 64, 128, + 256, 512, 1024, 2048, 4096, + 8192, 12288, 16384, 24576, 32768}; + return val < 0XF ? au[val] : 65536UL; + } + /** \return current bus width or -1 for error. */ + uint8_t busWidth() const { + uint8_t w = busWidthSecureMode >> 6; + return w == 2 ? 4 : w == 0 ? 1 : -1; + } + /** \return true is discard operation is supported else true. */ + bool discard() const { return discardFule & 2; } + /** \return eraseSize in AUs. */ + uint16_t eraseSizeAU() const { + return static_cast(eraseSize[0]) << 8 | + static_cast(eraseSize[1]); + } + /** \return eraseTimeout seconds. */ + uint8_t eraseTimeout() const { return eraseTimeoutOffset >> 2; } + /** \return eraseOffset seconds. */ + uint8_t eraseOffset() const { return eraseTimeoutOffset & 3; } + /** \return true if full user logical erase is supported else false. */ + bool fule() const { return discardFule & 1; } + /** \return true for secure mode else false. */ + bool secureMode() const { return busWidthSecureMode & 0X20; } + /** \return speed class or -1 for error. */ + int speedClass() const { + return speed < 4 ? 2 * speed : speed == 4 ? 10 : -1; + } + /** \return UHS Speed Grade. */ + int uhsClass() const { return uhsClassAuSize >> 4; } + /** \return Video Speed */ + int videoClass() { return videoSpeedClass; } +}; diff --git a/third_party/sdfat/src/SdCard/SdCardInterface.h b/third_party/sdfat/src/SdCard/SdCardInterface.h new file mode 100644 index 00000000..23f14922 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdCardInterface.h @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Abstract interface for an SD card. + */ +#pragma once +#include "../common/FsBlockDeviceInterface.h" +#include "SdCardInfo.h" +/** + * \class SdCardInterface + * \brief Abstract interface for an SD card. + */ +class SdCardInterface : public FsBlockDeviceInterface { + public: + /** CMD6 Switch mode: Check Function Set Function. + * \param[in] arg CMD6 argument. + * \param[out] status return status data. + * + * \return true for success or false for failure. + */ + virtual bool cardCMD6(uint32_t arg, uint8_t* status) = 0; + /** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \return true for success or false for failure. + */ + virtual bool erase(Sector_t firstSector, Sector_t lastSector) = 0; + /** \return error code. */ + virtual uint8_t errorCode() const = 0; + /** \return error data. */ + virtual uint32_t errorData() const = 0; + /** \return false by default */ + virtual bool hasDedicatedSpi() { return false; } + /** \return false by default */ + virtual bool isDedicatedSpi() { return false; } + /** \return false by default */ + virtual bool isSpi() { return false; } + /** Set SPI sharing state + * \param[in] value desired state. + * \return false by default. + */ + virtual bool setDedicatedSpi(bool value) { + (void)value; + return false; + } + /** + * Read a card's CID register. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + virtual bool readCID(cid_t* cid) = 0; + /** + * Read a card's CSD register. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + virtual bool readCSD(csd_t* csd) = 0; + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ + virtual bool readOCR(uint32_t* ocr) = 0; + /** Read SCR register. + * + * \param[out] scr Value of SCR register. + * \return true for success or false for failure. + */ + virtual bool readSCR(scr_t* scr) = 0; + /** Return the 64 byte SD Status register. + * \param[out] sds location for 64 status bytes. + * \return true for success or false for failure. + */ + virtual bool readSDS(sds_t* sds) = 0; + /** \return card status. */ + virtual uint32_t status() { return 0XFFFFFFFF; } + /** Return the card type: SD V1, SD V2 or SDHC/SDXC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC/SDXC. + */ + virtual uint8_t type() const = 0; +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SdSpiCard.cpp b/third_party/sdfat/src/SdCard/SdSpiCard/SdSpiCard.cpp new file mode 100644 index 00000000..bb491a11 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SdSpiCard.cpp @@ -0,0 +1,765 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiCard.h" +//============================================================================== +namespace { // Avoid conflict with another Timeout class. +class Timeout { + public: + Timeout() {} + explicit Timeout(uint16_t ms) { set(ms); } + uint16_t millis16() { return millis(); } + void set(uint16_t ms) { m_endTime = ms + millis16(); } + bool timedOut() { return static_cast(m_endTime - millis16()) < 0; } + + private: + uint16_t m_endTime; +}; +} // namespace +//============================================================================== +#if USE_SD_CRC +// CRC functions +//------------------------------------------------------------------------------ +static uint8_t CRC7(const uint8_t* data, uint8_t n) { + uint8_t crc = 0; + for (uint8_t i = 0; i < n; i++) { + uint8_t d = data[i]; + for (uint8_t j = 0; j < 8; j++) { + crc <<= 1; + if ((d & 0x80) ^ (crc & 0x80)) { + crc ^= 0x09; + } + d <<= 1; + } + } + return (crc << 1) | 1; +} +//------------------------------------------------------------------------------ +#if USE_SD_CRC == 1 +// Shift based CRC-CCITT +// uses the x^16,x^12,x^5,x^1 polynomial. +static uint16_t CRC_CCITT(const uint8_t* data, size_t n) { + uint16_t crc = 0; + for (size_t i = 0; i < n; i++) { + crc = (uint8_t)(crc >> 8) | (crc << 8); + crc ^= data[i]; + crc ^= (uint8_t)(crc & 0xff) >> 4; + crc ^= crc << 12; + crc ^= (crc & 0xff) << 5; + } + return crc; +} +#elif USE_SD_CRC > 1 // CRC_CCITT +//------------------------------------------------------------------------------ +// Table based CRC-CCITT +// uses the x^16,x^12,x^5,x^1 polynomial. +#ifdef __AVR__ +static const uint16_t crctab[] PROGMEM = { +#else // __AVR__ +static const uint16_t crctab[] = { +#endif // __AVR__ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, + 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, + 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, + 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, + 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, + 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, + 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, + 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, + 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, + 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, + 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, + 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, + 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, + 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, + 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, + 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, + 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, + 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, + 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, + 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, + 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, + 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, + 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, + 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, + 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; +static uint16_t CRC_CCITT(const uint8_t* data, size_t n) { + uint16_t crc = 0; + for (size_t i = 0; i < n; i++) { +#ifdef __AVR__ + crc = pgm_read_word(&crctab[(crc >> 8 ^ data[i]) & 0XFF]) ^ (crc << 8); +#else // __AVR__ + crc = crctab[(crc >> 8 ^ data[i]) & 0XFF] ^ (crc << 8); +#endif // __AVR__ + } + return crc; +} +#endif // CRC_CCITT +#endif // USE_SD_CRC +//============================================================================== +// SdSpiCard member functions +//------------------------------------------------------------------------------ +bool SdSpiCard::begin(SdSpiConfig spiConfig) { + uint8_t cardType; + uint32_t arg; + Timeout timeout; + // Restore state to creator. + initSharedSpiCard(); + m_errorCode = SD_CARD_ERROR_NONE; + m_csPin = spiConfig.csPin; +#if SPI_DRIVER_SELECT >= 2 + m_spiDriverPtr = spiConfig.spiPort; + if (!m_spiDriverPtr) { + sdError(SD_CARD_ERROR_INVALID_CARD_CONFIG); + goto fail; + } +#endif // SPI_DRIVER_SELECT + sdCsInit(m_csPin); + spiUnselect(); + spiSetSckSpeed(1000UL * SD_MAX_INIT_RATE_KHZ); + spiBegin(spiConfig); + m_beginCalled = true; + + spiStart(); + + // must supply min of 74 clock cycles with CS high. + spiUnselect(); + for (uint8_t i = 0; i < 10; i++) { + spiReceive(); + } + spiSelect(); + timeout.set(SD_INIT_TIMEOUT); + while (true) { + // command to go idle in SPI mode + if (cardCommand(CMD0, 0) == R1_IDLE_STATE) { + break; + } + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_CMD0); + goto fail; + } + } +#if USE_SD_CRC + if (cardCommand(CMD59, 1) != R1_IDLE_STATE) { + sdError(SD_CARD_ERROR_CMD59); + goto fail; + } +#endif // USE_SD_CRC + // check SD version + while (true) { + if (cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND) { + cardType = SD_CARD_TYPE_SD1; + break; + } + // Skip first three bytes. + for (uint8_t i = 0; i < 4; i++) { + m_status = spiReceive(); + } + if (m_status == 0XAA) { + cardType = SD_CARD_TYPE_SD2; + break; + } + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_CMD8); + goto fail; + } + } + // initialize card and send host supports SDHC if SD2 + arg = cardType == SD_CARD_TYPE_SD2 ? 0X40000000 : 0; + while (cardAcmd(ACMD41, arg) != R1_READY_STATE) { + // check for timeout + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_ACMD41); + goto fail; + } + } + // if SD2 read OCR register to check for SDHC card + if (cardType == SD_CARD_TYPE_SD2) { + if (cardCommand(CMD58, 0)) { + sdError(SD_CARD_ERROR_CMD58); + goto fail; + } + if ((spiReceive() & 0XC0) == 0XC0) { + cardType = SD_CARD_TYPE_SDHC; + } + // Discard rest of ocr - contains allowed voltage range. + for (uint8_t i = 0; i < 3; i++) { + spiReceive(); + } + } + spiStop(); + spiSetSckSpeed(spiConfig.maxSck); + m_type = cardType; +#if ENABLE_DEDICATED_SPI + m_dedicatedSpi = spiOptionDedicated(spiConfig.options); +#endif + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::cardCMD6(uint32_t arg, uint8_t* status) { + if (cardCommand(CMD6, arg)) { + sdError(SD_CARD_ERROR_CMD6); + goto fail; + } + if (!readData(status, 64)) { + goto fail; + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +// send command and return error code. Return zero for OK +uint8_t SdSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { + if (!syncDevice()) { + return 0XFF; + } + // select card + if (!m_spiActive) { + spiStart(); + } + if (cmd != CMD0 && cmd != CMD12 && !waitReady(SD_CMD_TIMEOUT)) { + return 0XFF; + } +#if USE_SD_CRC + // form message + uint8_t buf[6]; + buf[0] = (uint8_t)0x40U | cmd; + buf[1] = (uint8_t)(arg >> 24U); + buf[2] = (uint8_t)(arg >> 16U); + buf[3] = (uint8_t)(arg >> 8U); + buf[4] = (uint8_t)arg; + + // add CRC + buf[5] = CRC7(buf, 5); + + // send message + spiSend(buf, 6); +#else // USE_SD_CRC + // send command + spiSend(cmd | 0x40); + + // send argument + const uint8_t* pa = reinterpret_cast(&arg); + for (int8_t i = 3; i >= 0; i--) { + spiSend(pa[i]); + } + + // send CRC - correct for CMD0 with arg zero or CMD8 with arg 0X1AA + spiSend(cmd == CMD0 ? 0X95 : 0X87); +#endif // USE_SD_CRC + + // discard first fill byte to avoid MISO pull-up problem. + spiReceive(); + + // there are 1-8 fill bytes before response. fill bytes should be 0XFF. + uint8_t n = 0; + do { + m_status = spiReceive(); + } while (m_status & 0X80 && ++n < 10); + return m_status; +} +//------------------------------------------------------------------------------ +void SdSpiCard::end() { + if (m_beginCalled) { + syncDevice(); + spiEnd(); + m_beginCalled = false; + } +} +//------------------------------------------------------------------------------ +bool SdSpiCard::erase(Sector_t firstSector, Sector_t lastSector) { + csd_t csd; + if (!readCSD(&csd)) { + goto fail; + } + // check for single sector erase + if (!csd.eraseSingleBlock()) { + // erase size mask + uint8_t m = csd.eraseSize() - 1; + if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) { + // error card can't erase specified area + sdError(SD_CARD_ERROR_ERASE_SINGLE_SECTOR); + goto fail; + } + } + if (type() != SD_CARD_TYPE_SDHC) { + firstSector <<= 9; + lastSector <<= 9; + } + if (cardCommand(CMD32, firstSector) || cardCommand(CMD33, lastSector) || + cardCommand(CMD38, 0)) { + sdError(SD_CARD_ERROR_ERASE); + goto fail; + } + if (!waitReady(SD_ERASE_TIMEOUT)) { + sdError(SD_CARD_ERROR_ERASE_TIMEOUT); + goto fail; + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::eraseSingleSectorEnable() { + csd_t csd; + return readCSD(&csd) ? csd.eraseSingleBlock() : false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::isBusy() { + if (m_state == READ_STATE) { + return false; + } + bool spiActive = m_spiActive; + if (!spiActive) { + spiStart(); + } + bool rtn = 0XFF != spiReceive(); + if (!spiActive) { + spiStop(); + } + return rtn; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readData(uint8_t* dst) { return readData(dst, 512); } +//------------------------------------------------------------------------------ +bool SdSpiCard::readData(uint8_t* dst, size_t count) { +#if USE_SD_CRC + uint16_t crc; +#endif // USE_SD_CRC + + // wait for start sector token + Timeout timeout(SD_READ_TIMEOUT); + while ((m_status = spiReceive()) == 0XFF) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + if (m_status != DATA_START_SECTOR) { + sdError(SD_CARD_ERROR_READ_TOKEN); + goto fail; + } + // transfer data + // cppcheck wrong - Due can return non-zero. + // cppcheck-suppress knownConditionTrueFalse + if ((m_status = spiReceive(dst, count))) { + sdError(SD_CARD_ERROR_DMA); + goto fail; + } + +#if USE_SD_CRC + // get crc + crc = (spiReceive() << 8) | spiReceive(); + if (crc != CRC_CCITT(dst, count)) { + sdError(SD_CARD_ERROR_READ_CRC); + goto fail; + } +#else // USE_SD_CRC + // discard crc + spiReceive(); + spiReceive(); +#endif // USE_SD_CRC + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readOCR(uint32_t* ocr) { + uint8_t* p = reinterpret_cast(ocr); + if (cardCommand(CMD58, 0)) { + sdError(SD_CARD_ERROR_CMD58); + goto fail; + } + for (uint8_t i = 0; i < 4; i++) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + p[3 - i] = spiReceive(); +#else // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + p[i] = spiReceive(); +#endif // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +/** read CID or CSR register */ +bool SdSpiCard::readRegister(uint8_t cmd, void* buf) { + uint8_t* dst = reinterpret_cast(buf); + if (cardCommand(cmd, 0)) { + sdError(SD_CARD_ERROR_READ_REG); + goto fail; + } + if (!readData(dst, 16)) { + goto fail; + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readSCR(scr_t* scr) { + uint8_t* dst = reinterpret_cast(scr); + if (cardAcmd(ACMD51, 0)) { + sdError(SD_CARD_ERROR_ACMD51); + goto fail; + } + if (!readData(dst, sizeof(scr_t))) { + goto fail; + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readSector(Sector_t sector, uint8_t* dst) { +#if ENABLE_DEDICATED_SPI + return readSectors(sector, dst, 1); +#else + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD17, sector)) { + sdError(SD_CARD_ERROR_CMD17); + goto fail; + } + if (!readData(dst, 512)) { + goto fail; + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +#endif +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readSectors(Sector_t sector, uint8_t* dst, size_t ns) { +#if ENABLE_DEDICATED_SPI + if (sdState() != READ_STATE || sector != m_curSector) { + if (!readStart(sector)) { + goto fail; + } + m_curSector = sector; + } + for (size_t i = 0; i < ns; i++, dst += 512) { + if (!readData(dst)) { + goto fail; + } + } + m_curSector += ns; + return m_dedicatedSpi ? true : readStop(); +#else + if (!readStart(sector)) { + goto fail; + } + for (size_t i = 0; i < ns; i++, dst += 512) { + if (!readData(dst, 512)) { + goto fail; + } + } + return readStop(); +#endif +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readStart(Sector_t sector) { + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD18, sector)) { + sdError(SD_CARD_ERROR_CMD18); + goto fail; + } + m_state = READ_STATE; + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readSDS(sds_t* sds) { + uint8_t* dst = reinterpret_cast(sds); + // retrun is R2 so read extra status byte. + if (cardAcmd(ACMD13, 0) || spiReceive()) { + sdError(SD_CARD_ERROR_ACMD13); + goto fail; + } + if (!readData(dst, sizeof(sds_t))) { + goto fail; + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readStop() { + m_state = IDLE_STATE; + if (cardCommand(CMD12, 0)) { + sdError(SD_CARD_ERROR_CMD12); + goto fail; + } + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +Sector_t SdSpiCard::sectorCount() { + csd_t csd; + return readCSD(&csd) ? csd.capacity() : 0; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::setDedicatedSpi(bool value) { +#if ENABLE_DEDICATED_SPI + if (!syncDevice()) { + return false; + } + m_dedicatedSpi = value; + return true; +#else // ENABLE_DEDICATED_SPI + (void)value; + return false; +#endif // ENABLE_DEDICATED_SPI +} +//------------------------------------------------------------------------------ +void SdSpiCard::spiStart() { + SPI_ASSERT_NOT_ACTIVE; + if (!m_spiActive) { + spiActivate(); + m_spiActive = true; + spiSelect(); + // Dummy byte to drive MISO busy status. + spiSend(0XFF); + } +} +//------------------------------------------------------------------------------ +void SdSpiCard::spiStop() { + SPI_ASSERT_ACTIVE; + if (m_spiActive) { + spiUnselect(); + // Insure MISO goes to low Z. + spiSend(0XFF); + spiDeactivate(); + m_spiActive = false; + } +} +//------------------------------------------------------------------------------ +bool SdSpiCard::syncDevice() { + if (m_state == WRITE_STATE) { + return writeStop(); + } + if (m_state == READ_STATE) { + return readStop(); + } + return true; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::waitReady(uint16_t ms) { + Timeout timeout(ms); + while (spiReceive() != 0XFF) { + if (timeout.timedOut()) { + return false; + } + } + return true; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeData(const uint8_t* src) { + // wait for previous write to finish + if (!waitReady(SD_WRITE_TIMEOUT)) { + sdError(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + if (!writeData(WRITE_MULTIPLE_TOKEN, src)) { + goto fail; + } + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +// send one sector of data for write sector or write multiple sectors +bool SdSpiCard::writeData(uint8_t token, const uint8_t* src) { +#if USE_SD_CRC + uint16_t crc = CRC_CCITT(src, 512); +#else // USE_SD_CRC + uint16_t crc = 0XFFFF; +#endif // USE_SD_CRC + spiSend(token); + spiSend(src, 512); + spiSend(crc >> 8); + spiSend(crc & 0XFF); + + m_status = spiReceive(); + if ((m_status & DATA_RES_MASK) != DATA_RES_ACCEPTED) { + sdError(SD_CARD_ERROR_WRITE_DATA); + goto fail; + } + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeSector(Sector_t sector, const uint8_t* src) { +#ifndef OLD_WAY_WRITE_SECTOR +#if ENABLE_DEDICATED_SPI + if (m_dedicatedSpi) { + return writeSectors(sector, src, 1); + } +#endif +#endif // OLD_WAY_WRITE_SECTOR + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD24, sector)) { + sdError(SD_CARD_ERROR_CMD24); + goto fail; + } + if (!writeData(DATA_START_SECTOR, src)) { + goto fail; + } + +#if CHECK_FLASH_PROGRAMMING + // wait for flash programming to complete + if (!waitReady(SD_WRITE_TIMEOUT)) { + sdError(SD_CARD_ERROR_WRITE_PROGRAMMING); + goto fail; + } + // response is r2 so get and check two bytes for nonzero + if (cardCommand(CMD13, 0) || spiReceive()) { + sdError(SD_CARD_ERROR_CMD13); + goto fail; + } +#endif // CHECK_FLASH_PROGRAMMING + + spiStop(); + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeSectors(Sector_t sector, const uint8_t* src, size_t ns) { +#if ENABLE_DEDICATED_SPI + if (sdState() != WRITE_STATE || m_curSector != sector) { + if (!writeStart(sector)) { + goto fail; + } + m_curSector = sector; + } + for (size_t i = 0; i < ns; i++, src += 512) { + if (!writeData(src)) { + goto fail; + } + } + m_curSector += ns; + return m_dedicatedSpi ? true : writeStop(); +#else + if (!writeStart(sector)) { + goto fail; + } + for (size_t i = 0; i < ns; i++, src += 512) { + if (!writeData(src)) { + goto fail; + } + } + return writeStop(); +#endif +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeStart(Sector_t sector) { + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD25, sector)) { + sdError(SD_CARD_ERROR_CMD25); + goto fail; + } + m_state = WRITE_STATE; + return true; + +fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeStop() { + if (!waitReady(SD_WRITE_TIMEOUT)) { + goto fail; + } + spiSend(STOP_TRAN_TOKEN); + spiStop(); + m_state = IDLE_STATE; + return true; + +fail: + sdError(SD_CARD_ERROR_STOP_TRAN); + spiStop(); + return false; +} diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SdSpiCard.h b/third_party/sdfat/src/SdCard/SdSpiCard/SdSpiCard.h new file mode 100644 index 00000000..4be12a8b --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SdSpiCard.h @@ -0,0 +1,373 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Classes for SPI access to SD/SDHC cards. + */ +// cppcheck-suppress-file missingOverride +#pragma once +#include + +#include "../../common/SysCall.h" +#include "../SdCardInfo.h" +#include "../SdCardInterface.h" +#include "SpiDriver/SdSpiDriver.h" +/** Verify correct SPI active if non-zero. */ +#define CHECK_SPI_ACTIVE 0 +#if CHECK_SPI_ACTIVE +/** Check SPI active. */ +#define SPI_ASSERT_ACTIVE \ + { \ + if (!m_spiActive) { \ + Serial.print(F("SPI_ASSERT_ACTIVE")); \ + Serial.println(__LINE__); \ + while (true) { \ + } \ + } \ + } +#define SPI_ASSERT_NOT_ACTIVE \ + { \ + if (m_spiActive) { \ + Serial.print(F("SPI_ASSERT_NOT_ACTIVE")); \ + Serial.println(__LINE__); \ + while (true) { \ + } \ + } \ + } +#else // CHECK_SPI_ACTIVE +/** Check for SPI active. */ +#define SPI_ASSERT_ACTIVE +/** Check for SPI not active. */ +#define SPI_ASSERT_NOT_ACTIVE +#endif // CHECK_SPI_ACTIVE +//============================================================================== +/** + * \class SdSpiCard + * \brief Raw access to SD and SDHC flash memory cards via shared SPI port. + */ +#if HAS_SDIO_CLASS +class SdSpiCard : public SdCardInterface { +#elif USE_BLOCK_DEVICE_INTERFACE +class SdSpiCard : public FsBlockDeviceInterface { +#else // HAS_SDIO_CLASS +class SdSpiCard { +#endif // HAS_SDIO_CLASS + public: + /** SD is in idle state */ + static const uint8_t IDLE_STATE = 0; + /** SD is in multi-sector read state. */ + static const uint8_t READ_STATE = 1; + /** SD is in multi-sector write state. */ + static const uint8_t WRITE_STATE = 2; + /** Construct an instance of SdSpiCard. */ + SdSpiCard() { initSharedSpiCard(); } + /** Initialize the SD card. + * \param[in] spiConfig SPI card configuration. + * \return true for success or false for failure. + */ + bool begin(SdSpiConfig spiConfig); + /** CMD6 Switch mode: Check Function Set Function. + * \param[in] arg CMD6 argument. + * \param[out] status return status data. + * + * \return true for success or false for failure. + */ + bool cardCMD6(uint32_t arg, uint8_t* status); + /** End use of card */ + void end(); + /** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \note This function requests the SD card to do a flash erase for a + * range of sectors. The data on the card after an erase operation is + * either 0 or 1, depends on the card vendor. The card must support + * single sector erase. + * + * \return true for success or false for failure. + */ + bool erase(Sector_t firstSector, Sector_t lastSector); + /** Determine if card supports single sector erase. + * + * \return true is returned if single sector erase is supported. + * false is returned if single sector erase is not supported. + */ + bool eraseSingleSectorEnable(); + /** + * Set SD error code. + * \param[in] code value for error code. + */ + void sdError(uint8_t code) { m_errorCode = code; } + /** + * \return code for the last error. See SdCardInfo.h for a list of error + * codes. + */ + uint8_t errorCode() const { return m_errorCode; } + /** \return error data for last error. */ + uint32_t errorData() const { return m_status; } +/** \return false for shared class. */ +#if ENABLE_DEDICATED_SPI + bool hasDedicatedSpi() { return true; } +#else + bool hasDedicatedSpi() { return false; } +#endif + /** + * Check for busy. MISO low indicates the card is busy. + * + * \return true if busy else false. + */ + bool isBusy(); + /** \return true if in dedicated SPI state. */ +#if ENABLE_DEDICATED_SPI + bool isDedicatedSpi() { return m_dedicatedSpi; } +#else // ENABLE_DEDICATED_SPI + bool isDedicatedSpi() { return false; } +#endif // ENABLE_DEDICATED_SPI + /** \return true if card is on SPI bus. */ + bool isSpi() { return true; } + /** + * Read a card's CID register. The CID contains card identification + * information such as Manufacturer ID, Product name, Product serial + * number and Manufacturing date. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCID(cid_t* cid) { return readRegister(CMD10, cid); } + /** + * Read a card's CSD register. The CSD contains Card-Specific Data that + * provides information regarding access to the card's contents. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCSD(csd_t* csd) { return readRegister(CMD9, csd); } + /** Read one data sector in a multiple sector read sequence + * + * \param[out] dst Pointer to the location for the data to be read. + * + * \return true for success or false for failure. + */ + bool readData(uint8_t* dst); + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ + bool readOCR(uint32_t* ocr); + /** Read SCR register. + * + * \param[out] scr Value of SCR register. + * \return true for success or false for failure. + */ + bool readSCR(scr_t* scr); + /** + * Read a 512 byte sector from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSector(Sector_t sector, uint8_t* dst); + /** + * Read multiple 512 byte sectors from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSectors(Sector_t sector, uint8_t* dst, size_t ns); + /** Start a read multiple sector sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. SPI chipSelect must be low for the entire sequence. + * + * \return true for success or false for failure. + */ + bool readStart(Sector_t sector); + /** Return the 64 byte SD Status register. + * \param[out] status location for 64 status bytes. + * \return true for success or false for failure. + */ + bool readSDS(sds_t* status); + /** End a read multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool readStop(); + /** \return SD multi-sector read/write state */ + uint8_t sdState() { return m_state; } + /** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data sectors in the card + * or zero if an error occurs. + */ + Sector_t sectorCount(); + /** Set SPI sharing state + * \param[in] value desired state. + * \return true for success. + */ + bool setDedicatedSpi(bool value); + /** end a multi-sector transfer. + * + * \return true for success or false for failure. + */ + bool stopTransfer(); + /** \return success if sync successful. Not for user apps. */ + bool syncDevice(); + /** Return the card type: SD V1, SD V2 or SDHC/SDXC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC/SDXC. + */ + uint8_t type() const { return m_type; } + /** + * Write a 512 byte sector to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSector(Sector_t sector, const uint8_t* src); + /** + * Write multiple 512 byte sectors to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSectors(Sector_t sector, const uint8_t* src, size_t ns); + /** Write one data sector in a multiple sector write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeData(const uint8_t* src); + /** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ + bool writeStart(Sector_t sector); + + /** End a write multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool writeStop(); + + private: + // private functions + uint8_t cardAcmd(uint8_t cmd, uint32_t arg) { + cardCommand(CMD55, 0); + return cardCommand(cmd, arg); + } + uint8_t cardCommand(uint8_t cmd, uint32_t arg); + bool readData(uint8_t* dst, size_t count); + bool readRegister(uint8_t cmd, void* buf); + void spiSelect() { sdCsWrite(m_csPin, false); } + void spiStart(); + void spiStop(); + void spiUnselect() { sdCsWrite(m_csPin, true); } + bool waitReady(uint16_t ms); + bool writeData(uint8_t token, const uint8_t* src); +#if SPI_DRIVER_SELECT < 2 + void spiActivate() { m_spiDriver.activate(); } + void spiBegin(SdSpiConfig spiConfig) { m_spiDriver.begin(spiConfig); } + void spiDeactivate() { m_spiDriver.deactivate(); } + void spiEnd() { m_spiDriver.end(); } + uint8_t spiReceive() { + SPI_ASSERT_ACTIVE; + return m_spiDriver.receive(); + } + uint8_t spiReceive(uint8_t* buf, size_t n) { + SPI_ASSERT_ACTIVE; + return m_spiDriver.receive(buf, n); + } + void spiSend(uint8_t data) { + SPI_ASSERT_ACTIVE; + m_spiDriver.send(data); + } + void spiSend(const uint8_t* buf, size_t n) { + SPI_ASSERT_ACTIVE; + m_spiDriver.send(buf, n); + } + void spiSetSckSpeed(uint32_t maxSck) { m_spiDriver.setSckSpeed(maxSck); } + SdSpiDriver m_spiDriver; +#else // SPI_DRIVER_SELECT < 2 + void spiActivate() { m_spiDriverPtr->activate(); } + void spiBegin(SdSpiConfig spiConfig) { m_spiDriverPtr->begin(spiConfig); } + void spiDeactivate() { m_spiDriverPtr->deactivate(); } + void spiEnd() { m_spiDriverPtr->end(); } + uint8_t spiReceive() { + SPI_ASSERT_ACTIVE; + return m_spiDriverPtr->receive(); + } + uint8_t spiReceive(uint8_t* buf, size_t n) { + SPI_ASSERT_ACTIVE; + return m_spiDriverPtr->receive(buf, n); + } + void spiSend(uint8_t data) { + SPI_ASSERT_ACTIVE; + m_spiDriverPtr->send(data); + } + void spiSend(const uint8_t* buf, size_t n) { + SPI_ASSERT_ACTIVE; + m_spiDriverPtr->send(buf, n); + } + void spiSetSckSpeed(uint32_t maxSck) { m_spiDriverPtr->setSckSpeed(maxSck); } + SdSpiDriver* m_spiDriverPtr; + +#endif // SPI_DRIVER_SELECT < 2 + void initSharedSpiCard() { + m_beginCalled = false; + m_csPin = 0; + m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED; + m_spiActive = false; + m_state = IDLE_STATE; + m_status = 0; + m_type = 0; + } +#if ENABLE_DEDICATED_SPI + Sector_t m_curSector = 0; + bool m_dedicatedSpi = false; +#endif // ENABLE_DEDICATED_SPI + bool m_beginCalled; + SdCsPin_t m_csPin; + uint8_t m_errorCode; + bool m_spiActive; + uint8_t m_state; + uint8_t m_status; + uint8_t m_type; +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/DigitalPin.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/DigitalPin.h new file mode 100644 index 00000000..0d70a4c8 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/DigitalPin.h @@ -0,0 +1,379 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library 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 Library 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 the Arduino DigitalIO Library. If not, see + * . + */ +/** + * @file + * @brief Fast Digital Pin functions + * + * @defgroup digitalPin Fast Pin I/O + * @details Fast Digital I/O functions and template class. + * @{ + */ +#pragma once +#if defined(__AVR__) || defined(DOXYGEN) +#include +/** GpioPinMap type */ +struct GpioPinMap_t { + volatile uint8_t* pin; /**< address of PIN for this pin */ + volatile uint8_t* ddr; /**< address of DDR for this pin */ + volatile uint8_t* port; /**< address of PORT for this pin */ + uint8_t mask; /**< bit mask for this pin */ +}; + +/** Initializer macro. */ +#define GPIO_PIN(reg, bit) {&PIN##reg, &DDR##reg, &PORT##reg, 1 << bit} + +// Include pin map for current board. +#include "boards/GpioPinMap.h" +//------------------------------------------------------------------------------ +/** generate bad pin number error */ +void badPinNumber(void) + __attribute__((error("Pin number is too large or not a constant"))); +//------------------------------------------------------------------------------ +/** Check for valid pin number + * @param[in] pin Number of pin to be checked. + */ +static inline __attribute__((always_inline)) +void badPinCheck(uint8_t pin) { + if (!__builtin_constant_p(pin) || pin >= NUM_DIGITAL_PINS) { + badPinNumber(); + } +} +//------------------------------------------------------------------------------ +/** DDR register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* ddrReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].ddr; +} +//------------------------------------------------------------------------------ +/** Bit mask for pin + * @param[in] pin Arduino pin number + * @return mask + */ +static inline __attribute__((always_inline)) +uint8_t pinMask(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].mask; +} +//------------------------------------------------------------------------------ +/** PIN register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* pinReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].pin; +} +//------------------------------------------------------------------------------ +/** PORT register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* portReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].port; +} +//------------------------------------------------------------------------------ +/** Fast write helper. + * @param[in] address I/O register address + * @param[in] mask bit mask for pin + * @param[in] level value for bit + */ +static inline __attribute__((always_inline)) +void fastBitWriteSafe(volatile uint8_t* address, uint8_t mask, bool level) { + uint8_t s; + if (address > reinterpret_cast(0X3F)) { + s = SREG; + cli(); + } + if (level) { + *address |= mask; + } else { + *address &= ~mask; + } + if (address > reinterpret_cast(0X3F)) { + SREG = s; + } +} +//------------------------------------------------------------------------------ +/** Read pin value. + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return *pinReg(pin) & pinMask(pin); +} +//------------------------------------------------------------------------------ +/** Toggle a pin. + * @param[in] pin Arduino pin number + * + * If the pin is in output mode toggle the pin level. + * If the pin is in input mode toggle the state of the 20K pullup. + */ +static inline __attribute__((always_inline)) +void fastDigitalToggle(uint8_t pin) { + if (pinReg(pin) > reinterpret_cast(0X3F)) { + // must write bit to high address port + *pinReg(pin) = pinMask(pin); + } else { + // will compile to sbi and PIN register will not be read. + *pinReg(pin) |= pinMask(pin); + } +} +//------------------------------------------------------------------------------ +/** Set pin value. + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool level) { + fastBitWriteSafe(portReg(pin), pinMask(pin), level); +} +//------------------------------------------------------------------------------ +/** Write the DDR register. + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDdrWrite(uint8_t pin, bool level) { + fastBitWriteSafe(ddrReg(pin), pinMask(pin), level); +} +//------------------------------------------------------------------------------ +/** Set pin mode. + * @param[in] pin Arduino pin number + * @param[in] mode INPUT, OUTPUT, or INPUT_PULLUP. + * + * The internal pullup resistors will be enabled if mode is INPUT_PULLUP + * and disabled if the mode is INPUT. + */ +static inline __attribute__((always_inline)) +void fastPinMode(uint8_t pin, uint8_t mode) { + fastDdrWrite(pin, mode == OUTPUT); + if (mode != OUTPUT) { + fastDigitalWrite(pin, mode == INPUT_PULLUP); + } +} +#else // defined(__AVR__) +#if defined(CORE_TEENSY) +//------------------------------------------------------------------------------ +/** read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return *portInputRegister(pin); +} +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool value) { + if (value) { + *portSetRegister(pin) = 1; + } else { + *portClearRegister(pin) = 1; + } +} +#elif defined(__SAM3X8E__) || defined(__SAM3X8H__) +//------------------------------------------------------------------------------ +/** read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return g_APinDescription[pin].pPort->PIO_PDSR & g_APinDescription[pin].ulPin; +} +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool value) { + if (value) { + g_APinDescription[pin].pPort->PIO_SODR = g_APinDescription[pin].ulPin; + } else { + g_APinDescription[pin].pPort->PIO_CODR = g_APinDescription[pin].ulPin; + } +} +#elif defined(ESP8266) +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] val value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, uint8_t val) { + if (pin < 16) { + if (val) { + GPOS = (1 << pin); + } else { + GPOC = (1 << pin); + } + } else if (pin == 16) { + if (val) { + GP16O |= 1; + } else { + GP16O &= ~1; + } + } +} +//------------------------------------------------------------------------------ +/** Read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + if (pin < 16) { + return GPIP(pin); + } else if (pin == 16) { + return GP16I & 0x01; + } + return 0; +} +#else // CORE_TEENSY +//------------------------------------------------------------------------------ +inline void fastDigitalWrite(uint8_t pin, bool value) { + digitalWrite(pin, value); +} +//------------------------------------------------------------------------------ +inline bool fastDigitalRead(uint8_t pin) { + return digitalRead(pin); +} +#endif // CORE_TEENSY +//------------------------------------------------------------------------------ +inline void fastDigitalToggle(uint8_t pin) { + fastDigitalWrite(pin, !fastDigitalRead(pin)); +} +//------------------------------------------------------------------------------ +inline void fastPinMode(uint8_t pin, uint8_t mode) { + pinMode(pin, mode); +} +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** set pin configuration + * @param[in] pin Arduino pin number + * @param[in] mode mode INPUT or OUTPUT. + * @param[in] level If mode is output, set level high/low. + * If mode is input, enable or disable the pin's 20K pullup. + */ +#define fastPinConfig(pin, mode, level)\ + {fastPinMode(pin, mode); fastDigitalWrite(pin, level);} +//============================================================================== +/** + * @class DigitalPin + * @brief Fast digital port I/O + */ +template +class DigitalPin { + public: + //---------------------------------------------------------------------------- + /** Constructor */ + DigitalPin() {} + //---------------------------------------------------------------------------- + /** Asignment operator. + * @param[in] value If true set the pin's level high else set the + * pin's level low. + * + * @return This DigitalPin instance. + */ + inline DigitalPin & operator = (bool value) __attribute__((always_inline)) { + write(value); + return *this; + } + //---------------------------------------------------------------------------- + /** Parenthesis operator. + * @return Pin's level + */ + inline operator bool () const __attribute__((always_inline)) { + return read(); + } + //---------------------------------------------------------------------------- + /** Set pin configuration. + * @param[in] mode: INPUT or OUTPUT. + * @param[in] level If mode is OUTPUT, set level high/low. + * If mode is INPUT, enable or disable the pin's 20K pullup. + */ + inline __attribute__((always_inline)) + void config(uint8_t mode, bool level) { + fastPinConfig(PinNumber, mode, level); + } + //---------------------------------------------------------------------------- + /** + * Set pin level high if output mode or enable 20K pullup if input mode. + */ + inline __attribute__((always_inline)) + void high() {write(true);} + //---------------------------------------------------------------------------- + /** + * Set pin level low if output mode or disable 20K pullup if input mode. + */ + inline __attribute__((always_inline)) + void low() {write(false);} + //---------------------------------------------------------------------------- + /** + * Set pin mode. + * @param[in] mode: INPUT, OUTPUT, or INPUT_PULLUP. + * + * The internal pullup resistors will be enabled if mode is INPUT_PULLUP + * and disabled if the mode is INPUT. + */ + inline __attribute__((always_inline)) + void mode(uint8_t mode) { + fastPinMode(PinNumber, mode); + } + //---------------------------------------------------------------------------- + /** @return Pin's level. */ + inline __attribute__((always_inline)) + bool read() const { + return fastDigitalRead(PinNumber); + } + //---------------------------------------------------------------------------- + /** Toggle a pin. + * + * If the pin is in output mode toggle the pin's level. + * If the pin is in input mode toggle the state of the 20K pullup. + */ + inline __attribute__((always_inline)) + void toggle() { + fastDigitalToggle(PinNumber); + } + //---------------------------------------------------------------------------- + /** Write the pin's level. + * @param[in] value If true set the pin's level high else set the + * pin's level low. + */ + inline __attribute__((always_inline)) + void write(bool value) { + fastDigitalWrite(PinNumber, value); + } +}; +/** @} */ diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/SoftSPI.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/SoftSPI.h new file mode 100644 index 00000000..d22e6b49 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/SoftSPI.h @@ -0,0 +1,159 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library 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 Library 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 the Arduino DigitalIO Library. If not, see + * . + */ +/** + * @file + * @brief Software SPI. + * + * @defgroup softSPI Software SPI + * @details Software SPI Template Class. + * @{ + */ +#pragma once +#include "DigitalPin.h" +//------------------------------------------------------------------------------ +/** Nop for timing. */ +#define nop asm volatile ("nop\n\t") +//------------------------------------------------------------------------------ +/** Pin Mode for MISO is input.*/ +#define MISO_MODE INPUT +/** Pullups disabled for MISO are disabled. */ +#define MISO_LEVEL false +/** Pin Mode for MOSI is output.*/ +#define MOSI_MODE OUTPUT +/** Pin Mode for SCK is output. */ +#define SCK_MODE OUTPUT +//------------------------------------------------------------------------------ +/** + * @class SoftSPI + * @brief Fast software SPI. + */ +template +class SoftSPI { + public: + //---------------------------------------------------------------------------- + /** Initialize SoftSPI pins. */ + void begin() { + fastPinConfig(MisoPin, MISO_MODE, MISO_LEVEL); + fastPinConfig(MosiPin, MOSI_MODE, !MODE_CPHA(Mode)); + fastPinConfig(SckPin, SCK_MODE, MODE_CPOL(Mode)); + } + //---------------------------------------------------------------------------- + /** Soft SPI receive byte. + * @return Data byte received. + */ + inline __attribute__((always_inline)) + uint8_t receive() { + uint8_t data = 0; + receiveBit(7, &data); + receiveBit(6, &data); + receiveBit(5, &data); + receiveBit(4, &data); + receiveBit(3, &data); + receiveBit(2, &data); + receiveBit(1, &data); + receiveBit(0, &data); + return data; + } + //---------------------------------------------------------------------------- + /** Soft SPI send byte. + * @param[in] data Data byte to send. + */ + inline __attribute__((always_inline)) + void send(uint8_t data) { + sendBit(7, data); + sendBit(6, data); + sendBit(5, data); + sendBit(4, data); + sendBit(3, data); + sendBit(2, data); + sendBit(1, data); + sendBit(0, data); + } + //---------------------------------------------------------------------------- + /** Soft SPI transfer byte. + * @param[in] txData Data byte to send. + * @return Data byte received. + */ + inline __attribute__((always_inline)) + uint8_t transfer(uint8_t txData) { + uint8_t rxData = 0; + transferBit(7, &rxData, txData); + transferBit(6, &rxData, txData); + transferBit(5, &rxData, txData); + transferBit(4, &rxData, txData); + transferBit(3, &rxData, txData); + transferBit(2, &rxData, txData); + transferBit(1, &rxData, txData); + transferBit(0, &rxData, txData); + return rxData; + } + + private: + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + bool MODE_CPHA(uint8_t mode) {return (mode & 1) != 0;} + inline __attribute__((always_inline)) + bool MODE_CPOL(uint8_t mode) {return (mode & 2) != 0;} + inline __attribute__((always_inline)) + void receiveBit(uint8_t bit, uint8_t* data) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + nop; + nop; + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + if (fastDigitalRead(MisoPin)) *data |= 1 << bit; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + void sendBit(uint8_t bit, uint8_t data) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + fastDigitalWrite(MosiPin, data & (1 << bit)); + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + nop; + nop; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + void transferBit(uint8_t bit, uint8_t* rxData, uint8_t txData) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + fastDigitalWrite(MosiPin, txData & (1 << bit)); + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + if (fastDigitalRead(MisoPin)) *rxData |= 1 << bit; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- +}; +/** @} */ diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/AvrDevelopersGpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/AvrDevelopersGpioPinMap.h new file mode 100644 index 00000000..32891f2a --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/AvrDevelopersGpioPinMap.h @@ -0,0 +1,35 @@ +#pragma once +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 7), // D24 + GPIO_PIN(A, 6), // D25 + GPIO_PIN(A, 5), // D26 + GPIO_PIN(A, 4), // D27 + GPIO_PIN(A, 3), // D28 + GPIO_PIN(A, 2), // D29 + GPIO_PIN(A, 1), // D30 + GPIO_PIN(A, 0) // D31 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/BobuinoGpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/BobuinoGpioPinMap.h new file mode 100644 index 00000000..4114f22d --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/BobuinoGpioPinMap.h @@ -0,0 +1,35 @@ +#pragma once +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 0), // D24 + GPIO_PIN(A, 1), // D25 + GPIO_PIN(A, 2), // D26 + GPIO_PIN(A, 3), // D27 + GPIO_PIN(A, 4), // D28 + GPIO_PIN(A, 5), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/GpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/GpioPinMap.h new file mode 100644 index 00000000..c0f55b12 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/GpioPinMap.h @@ -0,0 +1,62 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library 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 Library 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 the Arduino DigitalIO Library. If not, see + * . + */ +#pragma once +#if defined(__AVR_ATmega168__)\ +||defined(__AVR_ATmega168P__)\ +||defined(__AVR_ATmega328P__) +// 168 and 328 Arduinos +#include "UnoGpioPinMap.h" +#elif defined(__AVR_ATmega1280__)\ +|| defined(__AVR_ATmega2560__) +// Mega ADK +#include "MegaGpioPinMap.h" +#elif defined(__AVR_ATmega32U4__) +#ifdef CORE_TEENSY +#include "Teensy2GpioPinMap.h" +#else // CORE_TEENSY +// Leonardo or Yun +#include "LeonardoGpioPinMap.h" +#endif // CORE_TEENSY +#elif defined(__AVR_AT90USB646__)\ +|| defined(__AVR_AT90USB1286__) +// Teensy++ 1.0 & 2.0 +#include "Teensy2ppGpioPinMap.h" +#elif defined(__AVR_ATmega1284P__)\ +|| defined(__AVR_ATmega1284__)\ +|| defined(__AVR_ATmega644P__)\ +|| defined(__AVR_ATmega644__)\ +|| defined(__AVR_ATmega64__)\ +|| defined(__AVR_ATmega32__)\ +|| defined(__AVR_ATmega324__)\ +|| defined(__AVR_ATmega16__) +#ifdef ARDUINO_1284P_AVR_DEVELOPERS +#include "AvrDevelopersGpioPinMap.h" +#elif defined(ARDUINO_1284P_BOBUINO) +#include "BobuinoGpioPinMap.h" +#elif defined(ARDUINO_1284P_SLEEPINGBEAUTY) +#include "SleepingBeautyGpioPinMap.h" +#elif defined(ARDUINO_1284P_STANDARD) +#include "Standard1284GpioPinMap.h" +#else // ARDUINO_1284P_SLEEPINGBEAUTY +#error Undefined variant 1284, 644, 324 +#endif // ARDUINO_1284P_SLEEPINGBEAUTY +#else // 1284P, 1284, 644 +#error Unknown board type. +#endif // end all boards diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/LeonardoGpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/LeonardoGpioPinMap.h new file mode 100644 index 00000000..381ae56a --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/LeonardoGpioPinMap.h @@ -0,0 +1,33 @@ +#pragma once +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 2), // D0 + GPIO_PIN(D, 3), // D1 + GPIO_PIN(D, 1), // D2 + GPIO_PIN(D, 0), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(C, 6), // D5 + GPIO_PIN(D, 7), // D6 + GPIO_PIN(E, 6), // D7 + GPIO_PIN(B, 4), // D8 + GPIO_PIN(B, 5), // D9 + GPIO_PIN(B, 6), // D10 + GPIO_PIN(B, 7), // D11 + GPIO_PIN(D, 6), // D12 + GPIO_PIN(C, 7), // D13 + GPIO_PIN(B, 3), // D14 + GPIO_PIN(B, 1), // D15 + GPIO_PIN(B, 2), // D16 + GPIO_PIN(B, 0), // D17 + GPIO_PIN(F, 7), // D18 + GPIO_PIN(F, 6), // D19 + GPIO_PIN(F, 5), // D20 + GPIO_PIN(F, 4), // D21 + GPIO_PIN(F, 1), // D22 + GPIO_PIN(F, 0), // D23 + GPIO_PIN(D, 4), // D24 + GPIO_PIN(D, 7), // D25 + GPIO_PIN(B, 4), // D26 + GPIO_PIN(B, 5), // D27 + GPIO_PIN(B, 6), // D28 + GPIO_PIN(D, 6) // D29 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/MegaGpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/MegaGpioPinMap.h new file mode 100644 index 00000000..591a9b8e --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/MegaGpioPinMap.h @@ -0,0 +1,73 @@ +#pragma once +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(E, 0), // D0 + GPIO_PIN(E, 1), // D1 + GPIO_PIN(E, 4), // D2 + GPIO_PIN(E, 5), // D3 + GPIO_PIN(G, 5), // D4 + GPIO_PIN(E, 3), // D5 + GPIO_PIN(H, 3), // D6 + GPIO_PIN(H, 4), // D7 + GPIO_PIN(H, 5), // D8 + GPIO_PIN(H, 6), // D9 + GPIO_PIN(B, 4), // D10 + GPIO_PIN(B, 5), // D11 + GPIO_PIN(B, 6), // D12 + GPIO_PIN(B, 7), // D13 + GPIO_PIN(J, 1), // D14 + GPIO_PIN(J, 0), // D15 + GPIO_PIN(H, 1), // D16 + GPIO_PIN(H, 0), // D17 + GPIO_PIN(D, 3), // D18 + GPIO_PIN(D, 2), // D19 + GPIO_PIN(D, 1), // D20 + GPIO_PIN(D, 0), // D21 + GPIO_PIN(A, 0), // D22 + GPIO_PIN(A, 1), // D23 + GPIO_PIN(A, 2), // D24 + GPIO_PIN(A, 3), // D25 + GPIO_PIN(A, 4), // D26 + GPIO_PIN(A, 5), // D27 + GPIO_PIN(A, 6), // D28 + GPIO_PIN(A, 7), // D29 + GPIO_PIN(C, 7), // D30 + GPIO_PIN(C, 6), // D31 + GPIO_PIN(C, 5), // D32 + GPIO_PIN(C, 4), // D33 + GPIO_PIN(C, 3), // D34 + GPIO_PIN(C, 2), // D35 + GPIO_PIN(C, 1), // D36 + GPIO_PIN(C, 0), // D37 + GPIO_PIN(D, 7), // D38 + GPIO_PIN(G, 2), // D39 + GPIO_PIN(G, 1), // D40 + GPIO_PIN(G, 0), // D41 + GPIO_PIN(L, 7), // D42 + GPIO_PIN(L, 6), // D43 + GPIO_PIN(L, 5), // D44 + GPIO_PIN(L, 4), // D45 + GPIO_PIN(L, 3), // D46 + GPIO_PIN(L, 2), // D47 + GPIO_PIN(L, 1), // D48 + GPIO_PIN(L, 0), // D49 + GPIO_PIN(B, 3), // D50 + GPIO_PIN(B, 2), // D51 + GPIO_PIN(B, 1), // D52 + GPIO_PIN(B, 0), // D53 + GPIO_PIN(F, 0), // D54 + GPIO_PIN(F, 1), // D55 + GPIO_PIN(F, 2), // D56 + GPIO_PIN(F, 3), // D57 + GPIO_PIN(F, 4), // D58 + GPIO_PIN(F, 5), // D59 + GPIO_PIN(F, 6), // D60 + GPIO_PIN(F, 7), // D61 + GPIO_PIN(K, 0), // D62 + GPIO_PIN(K, 1), // D63 + GPIO_PIN(K, 2), // D64 + GPIO_PIN(K, 3), // D65 + GPIO_PIN(K, 4), // D66 + GPIO_PIN(K, 5), // D67 + GPIO_PIN(K, 6), // D68 + GPIO_PIN(K, 7) // D69 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/SleepingBeautyGpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/SleepingBeautyGpioPinMap.h new file mode 100644 index 00000000..86e7ca92 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/SleepingBeautyGpioPinMap.h @@ -0,0 +1,34 @@ +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(B, 0), // D4 + GPIO_PIN(B, 1), // D5 + GPIO_PIN(B, 2), // D6 + GPIO_PIN(B, 3), // D7 + GPIO_PIN(D, 6), // D8 + GPIO_PIN(D, 5), // D9 + GPIO_PIN(B, 4), // D10 + GPIO_PIN(B, 5), // D11 + GPIO_PIN(B, 6), // D12 + GPIO_PIN(B, 7), // D13 + GPIO_PIN(C, 7), // D14 + GPIO_PIN(C, 6), // D15 + GPIO_PIN(A, 5), // D16 + GPIO_PIN(A, 4), // D17 + GPIO_PIN(A, 3), // D18 + GPIO_PIN(A, 2), // D19 + GPIO_PIN(A, 1), // D20 + GPIO_PIN(A, 0), // D21 + GPIO_PIN(D, 4), // D22 + GPIO_PIN(D, 7), // D23 + GPIO_PIN(C, 2), // D24 + GPIO_PIN(C, 3), // D25 + GPIO_PIN(C, 4), // D26 + GPIO_PIN(C, 5), // D27 + GPIO_PIN(C, 1), // D28 + GPIO_PIN(C, 0), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Standard1284GpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Standard1284GpioPinMap.h new file mode 100644 index 00000000..4114f22d --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Standard1284GpioPinMap.h @@ -0,0 +1,35 @@ +#pragma once +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 0), // D24 + GPIO_PIN(A, 1), // D25 + GPIO_PIN(A, 2), // D26 + GPIO_PIN(A, 3), // D27 + GPIO_PIN(A, 4), // D28 + GPIO_PIN(A, 5), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Teensy2GpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Teensy2GpioPinMap.h new file mode 100644 index 00000000..faac772e --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Teensy2GpioPinMap.h @@ -0,0 +1,28 @@ +#pragma once +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 7), // D4 + GPIO_PIN(D, 0), // D5 + GPIO_PIN(D, 1), // D6 + GPIO_PIN(D, 2), // D7 + GPIO_PIN(D, 3), // D8 + GPIO_PIN(C, 6), // D9 + GPIO_PIN(C, 7), // D10 + GPIO_PIN(D, 6), // D11 + GPIO_PIN(D, 7), // D12 + GPIO_PIN(B, 4), // D13 + GPIO_PIN(B, 5), // D14 + GPIO_PIN(B, 6), // D15 + GPIO_PIN(F, 7), // D16 + GPIO_PIN(F, 6), // D17 + GPIO_PIN(F, 5), // D18 + GPIO_PIN(F, 4), // D19 + GPIO_PIN(F, 1), // D20 + GPIO_PIN(F, 0), // D21 + GPIO_PIN(D, 4), // D22 + GPIO_PIN(D, 5), // D23 + GPIO_PIN(E, 6), // D24 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Teensy2ppGpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Teensy2ppGpioPinMap.h new file mode 100644 index 00000000..40eb8a67 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/Teensy2ppGpioPinMap.h @@ -0,0 +1,48 @@ +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(D, 5), // D5 + GPIO_PIN(D, 6), // D6 + GPIO_PIN(D, 7), // D7 + GPIO_PIN(E, 0), // D8 + GPIO_PIN(E, 1), // D9 + GPIO_PIN(C, 0), // D10 + GPIO_PIN(C, 1), // D11 + GPIO_PIN(C, 2), // D12 + GPIO_PIN(C, 3), // D13 + GPIO_PIN(C, 4), // D14 + GPIO_PIN(C, 5), // D15 + GPIO_PIN(C, 6), // D16 + GPIO_PIN(C, 7), // D17 + GPIO_PIN(E, 6), // D18 + GPIO_PIN(E, 7), // D19 + GPIO_PIN(B, 0), // D20 + GPIO_PIN(B, 1), // D21 + GPIO_PIN(B, 2), // D22 + GPIO_PIN(B, 3), // D23 + GPIO_PIN(B, 4), // D24 + GPIO_PIN(B, 5), // D25 + GPIO_PIN(B, 6), // D26 + GPIO_PIN(B, 7), // D27 + GPIO_PIN(A, 0), // D28 + GPIO_PIN(A, 1), // D29 + GPIO_PIN(A, 2), // D30 + GPIO_PIN(A, 3), // D31 + GPIO_PIN(A, 4), // D32 + GPIO_PIN(A, 5), // D33 + GPIO_PIN(A, 6), // D34 + GPIO_PIN(A, 7), // D35 + GPIO_PIN(E, 4), // D36 + GPIO_PIN(E, 5), // D37 + GPIO_PIN(F, 0), // D38 + GPIO_PIN(F, 1), // D39 + GPIO_PIN(F, 2), // D40 + GPIO_PIN(F, 3), // D41 + GPIO_PIN(F, 4), // D42 + GPIO_PIN(F, 5), // D43 + GPIO_PIN(F, 6), // D44 + GPIO_PIN(F, 7), // D45 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/UnoGpioPinMap.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/UnoGpioPinMap.h new file mode 100644 index 00000000..39aea43a --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/boards/UnoGpioPinMap.h @@ -0,0 +1,22 @@ +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(D, 5), // D5 + GPIO_PIN(D, 6), // D6 + GPIO_PIN(D, 7), // D7 + GPIO_PIN(B, 0), // D8 + GPIO_PIN(B, 1), // D9 + GPIO_PIN(B, 2), // D10 + GPIO_PIN(B, 3), // D11 + GPIO_PIN(B, 4), // D12 + GPIO_PIN(B, 5), // D13 + GPIO_PIN(C, 0), // D14 + GPIO_PIN(C, 1), // D15 + GPIO_PIN(C, 2), // D16 + GPIO_PIN(C, 3), // D17 + GPIO_PIN(C, 4), // D18 + GPIO_PIN(C, 5) // D19 +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/readme.txt b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/readme.txt new file mode 100644 index 00000000..8976c650 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/DigitalIO/readme.txt @@ -0,0 +1,3 @@ +Selected files from the DigitalIO library. + +https://github.com/greiman/DigitalIO \ No newline at end of file diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiArduinoDriver.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiArduinoDriver.h new file mode 100644 index 00000000..561aaa8c --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiArduinoDriver.h @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief SpiDriver classes for Arduino compatible systems. + */ +#pragma once +//============================================================================== +#if SPI_DRIVER_SELECT == 0 && SD_HAS_CUSTOM_SPI +#define SD_USE_CUSTOM_SPI 1 +#endif // SPI_DRIVER_SELECT == 0 && SD_HAS_CUSTOM_SPI +/** + * \class SdSpiArduinoDriver + * \brief Optimized SPI class for access to SD and SDHC flash memory cards. + */ +class SdSpiArduinoDriver { + public: + /** Constructor. */ + SdSpiArduinoDriver() = default; + /** Activate SPI hardware. */ + void activate(); + /** Initialize the SPI bus. + * + * \param[in] spiConfig SD card configuration. + */ + void begin(SdSpiConfig spiConfig); + /** Deactivate SPI hardware. */ + void deactivate(); + /** End use of SPI driver after begin() call. */ + void end(); + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive(); + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t count); + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data); + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + void send(const uint8_t* buf, size_t count); + /** Save high speed SPISettings after SD initialization. + * + * \param[in] maxSck Maximum SCK frequency. + */ + void setSckSpeed(uint32_t maxSck) { + m_spiSettings = SPISettings(maxSck, MSBFIRST, SPI_MODE0); + } + + private: + SPIClass* m_spi = nullptr; + SPISettings m_spiSettings; +}; +/** Typedef for use of SdSpiArduinoDriver */ +typedef SdSpiArduinoDriver SdSpiDriver; +//------------------------------------------------------------------------------ +#ifndef SD_USE_CUSTOM_SPI +#include "SdSpiLibDriver.h" +#elif defined(__AVR__) +// Use custom AVR SPI driver. Other custom SPI drivers are in .cpp files. +#include "SdSpiAvr.h" +#endif // SD_USE_CUSTOM_SPI diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiAvr.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiAvr.h new file mode 100644 index 00000000..b4ff462e --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiAvr.h @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +// Use of in-line for AVR to save flash. +#define nop asm volatile("nop\n\t") +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::activate() { + SPI.beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + (void)spiConfig; + SPI.begin(); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::deactivate() { SPI.endTransaction(); } +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::end() { SPI.end(); } +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive() { return SPI.transfer(0XFF); } +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { + if (count == 0) { + return 0; + } +#ifdef SPSR + SPDR = 0XFF; + while (--count) { + // nops optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + while (!(SPSR & _BV(SPIF))) { + } + uint8_t in = SPDR; + SPDR = 0XFF; + *buf++ = in; + } + while (!(SPSR & _BV(SPIF))) { + } + *buf = SPDR; +#elif defined(SPI_RXCIF_bm) + SPI0.DATA = 0XFF; + while (--count) { + // nops optimize loop for ATmega4809 16MHz CPU 8 MHz SPI + nop; + nop; + nop; + nop; + while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) { + } + uint8_t in = SPI0.DATA; + SPI0.DATA = 0XFF; + *buf++ = in; + } + while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) { + } + *buf = SPI0.DATA; +#else // SPSR +#error Unsupported AVR CPU - edit SdFatConfig.h to use standard SPI library. +#endif // SPSR + return 0; +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(uint8_t data) { SPI.transfer(data); } +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { + if (count == 0) { + return; + } +#ifdef SPSR + SPDR = *buf++; + while (--count) { + uint8_t b = *buf++; + // nops optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + while (!(SPSR & (1 << SPIF))) { + } + SPDR = b; + } + while (!(SPSR & (1 << SPIF))) { + } +#elif defined(SPI_RXCIF_bm) + SPI0.DATA = *buf++; + while (--count) { + uint8_t b = *buf++; + // nops optimize loop for ATmega4809 16MHz CPU 8 MHz SPI + nop; + nop; + nop; + while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) { + } + SPI0.DATA = b; + } + while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) { + } +#else // SPSR +#error Unsupported AVR CPU - edit SdFatConfig.h to use standard SPI library. +#endif // SPSR +} diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiBareUnoDriver.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiBareUnoDriver.h new file mode 100644 index 00000000..69475389 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiBareUnoDriver.h @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief Driver to test with no Arduino includes. + */ + +#include + +#include "../../../common/SysCall.h" +#define nop asm volatile("nop\n\t") +#ifndef HIGH +#define HIGH 1 +#endif // HIGH +#ifndef LOW +#define LOW 0 +#endif // LOW +#ifndef INPUT +#define INPUT 0 +#endif // INPUT +#ifndef OUTPUT +#define OUTPUT 1 +#endif // OUTPUT + +inline uint8_t unoBit(uint8_t pin) { + return 1 << (pin < 8 ? pin : pin < 14 ? pin - 8 : pin - 14); +} +inline uint8_t unoDigitalRead(uint8_t pin) { + volatile uint8_t* reg = pin < 8 ? &PIND : pin < 14 ? &PINB : &PINC; + return *reg & unoBit(pin); +} +inline void unoDigitalWrite(uint8_t pin, uint8_t value) { + volatile uint8_t* port = pin < 8 ? &PORTD : pin < 14 ? &PORTB : &PORTC; + uint8_t bit = unoBit(pin); + cli(); + if (value) { + *port |= bit; + } else { + *port &= ~bit; + } + sei(); +} + +inline void unoPinMode(uint8_t pin, uint8_t mode) { + uint8_t bit = unoBit(pin); + volatile uint8_t* reg = pin < 8 ? &DDRD : pin < 14 ? &DDRB : &DDRC; + + cli(); + if (mode == OUTPUT) { + *reg |= bit; + } else { + *reg &= ~bit; + // handle INPUT pull-up + unoDigitalWrite(pin, mode != INPUT); + } + sei(); +} + +#define UNO_SS 10 +#define UNO_MOSI 11 +#define UNO_MISO 12 +#define UNO_SCK 13 +//------------------------------------------------------------------------------ +/** + * \class SdSpiDriverBareUno + * \brief Optimized SPI class for access to SD and SDHC flash memory cards. + */ +class SdSpiDriverBareUno { + public: + /** Activate SPI hardware. */ + void activate() {} + /** Initialize the SPI bus. + * + * \param[in] spiConfig SD card configuration. + */ + void begin(SdSpiConfig spiConfig) { + m_csPin = spiConfig.csPin; + unoPinMode(m_csPin, OUTPUT); + unoDigitalWrite(m_csPin, HIGH); + unoDigitalWrite(UNO_SS, HIGH); + unoPinMode(UNO_SS, OUTPUT); + SPCR |= _BV(MSTR); + SPCR |= _BV(SPE); + SPSR = 0; + unoPinMode(UNO_SCK, OUTPUT); + unoPinMode(UNO_MOSI, OUTPUT); + } + /** Deactivate SPI hardware. */ + void deactivate() {} + /** deactivate SPI driver. */ + void end() {} + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive() { return transfer(0XFF); } + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t count) { + if (count == 0) { + return 0; + } + uint8_t* pr = buf; + SPDR = 0XFF; + while (--count > 0) { + while (!(SPSR & _BV(SPIF))) { + } + uint8_t in = SPDR; + SPDR = 0XFF; + *pr++ = in; + // nops to optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + } + while (!(SPSR & _BV(SPIF))) { + } + *pr = SPDR; + return 0; + } + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data) { transfer(data); } + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + void send(const uint8_t* buf, size_t count) { + if (count == 0) { + return; + } + SPDR = *buf++; + while (--count > 0) { + uint8_t b = *buf++; + while (!(SPSR & (1 << SPIF))) { + } + SPDR = b; + // nops to optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + } + while (!(SPSR & (1 << SPIF))) { + } + } + /** Set CS low. */ + void select() { unoDigitalWrite(m_csPin, LOW); } + /** Save high speed SPISettings after SD initialization. + * + * \param[in] spiConfig SPI options. + */ + void setSckSpeed(uint32_t maxSck) { + (void)maxSck; + SPSR |= 1 << SPI2X; + } + static uint8_t transfer(uint8_t data) { + SPDR = data; + while (!(SPSR & _BV(SPIF))) { + } // wait + return SPDR; + } + /** Set CS high. */ + void unselect() { unoDigitalWrite(m_csPin, HIGH); } + + private: + SdCsPin_t m_csPin; +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiBaseClass.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiBaseClass.h new file mode 100644 index 00000000..568a3d7d --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiBaseClass.h @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Base class for external SPI driver. + */ +#pragma once +/** + * \class SdSpiBaseClass + * \brief Base class for external SPI drivers + */ +class SdSpiBaseClass { + public: + /** Activate SPI hardware. */ + virtual void activate() {} + /** Initialize the SPI bus. + * + * \param[in] config SPI configuration. + */ + virtual void begin(SdSpiConfig config) = 0; + /** Deactivate SPI hardware. */ + virtual void deactivate() {} + /** deactivate SPI driver. */ + virtual void end() {} + /** Receive a byte. + * + * \return The byte. + */ + virtual uint8_t receive() = 0; + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + virtual uint8_t receive(uint8_t* buf, size_t count) = 0; + /** Send a byte. + * + * \param[in] data Byte to send + */ + virtual void send(uint8_t data) = 0; + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + virtual void send(const uint8_t* buf, size_t count) = 0; + /** Save high speed SPISettings after SD initialization. + * + * \param[in] maxSck Maximum SCK frequency. + */ + virtual void setSckSpeed(uint32_t maxSck) { (void)maxSck; } +}; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiChipSelect.cpp b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiChipSelect.cpp new file mode 100644 index 00000000..836a9e59 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiChipSelect.cpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if ENABLE_ARDUINO_FEATURES +#if SD_CHIP_SELECT_MODE == 0 +//------------------------------------------------------------------------------ +void sdCsInit(SdCsPin_t pin) { pinMode(pin, OUTPUT); } +//------------------------------------------------------------------------------ +void sdCsWrite(SdCsPin_t pin, bool level) { + digitalWrite(pin, level ? HIGH : LOW); +} +#elif SD_CHIP_SELECT_MODE == 1 +//------------------------------------------------------------------------------ +__attribute__((weak)) void sdCsInit(SdCsPin_t pin) { pinMode(pin, OUTPUT); } +//------------------------------------------------------------------------------ +__attribute__((weak)) void sdCsWrite(SdCsPin_t pin, bool level) { + digitalWrite(pin, level ? HIGH : LOW); +} +#endif // SD_CHIP_SELECT_MODE == 0 +#endif // ENABLE_ARDUINO_FEATURES diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiDriver.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiDriver.h new file mode 100644 index 00000000..8f1c5fd6 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiDriver.h @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief SpiDriver classes + */ +#pragma once +#include "../../../common/SysCall.h" +/** + * Initialize SD chip select pin. + * + * \param[in] pin SD card chip select pin. + */ +void sdCsInit(SdCsPin_t pin); +/** + * Initialize SD chip select pin. + * + * \param[in] pin SD card chip select pin. + * \param[in] level SD card chip select level. + */ +void sdCsWrite(SdCsPin_t pin, bool level); +//------------------------------------------------------------------------------ +/** SPI bus is share with other devices. */ +const uint8_t SHARED_SPI = 0; +#if ENABLE_DEDICATED_SPI +/** The SD is the only device on the SPI bus. */ +const uint8_t DEDICATED_SPI = 1; +/** + * \param[in] opt option field of SdSpiConfig. + * \return true for dedicated SPI. + */ +inline bool spiOptionDedicated(uint8_t opt) { return opt & DEDICATED_SPI; } +#else // ENABLE_DEDICATED_SPI +/** + * \param[in] opt option field of SdSpiConfig. + * \return true for dedicated SPI. + */ +inline bool spiOptionDedicated(uint8_t opt) { + (void)opt; + return false; +} +#endif // ENABLE_DEDICATED_SPI +/** The user will call begin. Useful for custom SPI configurations. */ +const uint8_t USER_SPI_BEGIN = 2; +//------------------------------------------------------------------------------ +/** SPISettings for SCK frequency in Hz. */ +#define SD_SCK_HZ(maxSpeed) (maxSpeed) +/** SPISettings for SCK frequency in MHz. */ +#define SD_SCK_MHZ(maxMhz) (1000000UL * (maxMhz)) +// SPI divisor constants - obsolete. +/** Set SCK to max rate. */ +#define SPI_FULL_SPEED SD_SCK_MHZ(50) +/** Set SCK rate to 16 MHz for Due */ +#define SPI_DIV3_SPEED SD_SCK_MHZ(16) +/** Set SCK rate to 4 MHz for AVR. */ +#define SPI_HALF_SPEED SD_SCK_MHZ(4) +/** Set SCK rate to 8 MHz for Due */ +#define SPI_DIV6_SPEED SD_SCK_MHZ(8) +/** Set SCK rate to 2 MHz for AVR. */ +#define SPI_QUARTER_SPEED SD_SCK_MHZ(2) +/** Set SCK rate to 1 MHz for AVR. */ +#define SPI_EIGHTH_SPEED SD_SCK_MHZ(1) +/** Set SCK rate to 500 kHz for AVR. */ +#define SPI_SIXTEENTH_SPEED SD_SCK_HZ(500000) +//------------------------------------------------------------------------------ +#if SPI_DRIVER_SELECT < 2 +#include +/** Port type for Arduino SPI hardware driver. */ +typedef SPIClass SpiPort_t; +#elif SPI_DRIVER_SELECT == 2 +class SdSpiSoftDriver; +/** Port type for software SPI driver. */ +typedef SdSpiSoftDriver SpiPort_t; +#elif SPI_DRIVER_SELECT == 3 +class SdSpiBaseClass; +/** Port type for external SPI driver. */ +typedef SdSpiBaseClass SpiPort_t; +#else // SPI_DRIVER_SELECT +typedef void* SpiPort_t; +#endif // SPI_DRIVER_SELECT +//------------------------------------------------------------------------------ +/** + * \class SdSpiConfig + * \brief SPI card configuration. + */ +class SdSpiConfig { + public: + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + * \param[in] opt Options. + * \param[in] maxSpeed Maximum SCK frequency. + * \param[in] port The SPI port to use. + */ + SdSpiConfig(SdCsPin_t cs, uint8_t opt, uint32_t maxSpeed, SpiPort_t* port) + : csPin(cs), options(opt), maxSck(maxSpeed), spiPort(port) {} + + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + * \param[in] opt Options. + * \param[in] maxSpeed Maximum SCK frequency. + */ + SdSpiConfig(SdCsPin_t cs, uint8_t opt, uint32_t maxSpeed) + : csPin(cs), options(opt), maxSck(maxSpeed) {} + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + * \param[in] opt Options. + */ + SdSpiConfig(SdCsPin_t cs, uint8_t opt) : csPin(cs), options(opt) {} + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + */ + explicit SdSpiConfig(SdCsPin_t cs) : csPin(cs) {} + + /** Chip select pin. */ + const SdCsPin_t csPin; + /** Options */ + const uint8_t options = SHARED_SPI; + /** Max SCK frequency */ + const uint32_t maxSck = SD_SCK_MHZ(50); + /** SPI port */ + SpiPort_t* spiPort = nullptr; +}; +#if SPI_DRIVER_SELECT < 2 +#include "SdSpiArduinoDriver.h" +#elif SPI_DRIVER_SELECT == 2 +#include "SdSpiSoftDriver.h" +#elif SPI_DRIVER_SELECT == 3 +#include "SdSpiBaseClass.h" +typedef SdSpiBaseClass SdSpiDriver; +#else // SPI_DRIVER_SELECT +#error Invalid SPI_DRIVER_SELECT +#endif // SPI_DRIVER_SELECT diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiDue.cpp b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiDue.cpp new file mode 100644 index 00000000..72c4faca --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiDue.cpp @@ -0,0 +1,221 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +// cppcheck-suppress-file [redundantAssignment, constStatement] +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_SAM_DUE) +/* Use SAM3X DMAC if nonzero */ +#define USE_SAM3X_DMAC 1 +/* Use extra Bus Matrix arbitration fix if nonzero */ +#define USE_SAM3X_BUS_MATRIX_FIX 1 +/* Time in ms for DMA receive timeout */ +#define SAM3X_DMA_TIMEOUT 100 +/* chip select register number */ +#define SPI_CHIP_SEL 3 +/* DMAC receive channel */ +#define SPI_DMAC_RX_CH 1 +/* DMAC transmit channel */ +#define SPI_DMAC_TX_CH 0 +/* DMAC Channel HW Interface Number for SPI TX. */ +#define SPI_TX_IDX 1 +/* DMAC Channel HW Interface Number for SPI RX. */ +#define SPI_RX_IDX 2 +//------------------------------------------------------------------------------ +/* Disable DMA Controller. */ +static void dmac_disable() { DMAC->DMAC_EN &= (~DMAC_EN_ENABLE); } +/* Enable DMA Controller. */ +static void dmac_enable() { DMAC->DMAC_EN = DMAC_EN_ENABLE; } +/* Disable DMA Channel. */ +static void dmac_channel_disable(uint32_t ul_num) { + DMAC->DMAC_CHDR = DMAC_CHDR_DIS0 << ul_num; +} +/* Enable DMA Channel. */ +static void dmac_channel_enable(uint32_t ul_num) { + DMAC->DMAC_CHER = DMAC_CHER_ENA0 << ul_num; +} +/* Poll for transfer complete. */ +static bool dmac_channel_transfer_done(uint32_t ul_num) { + return (DMAC->DMAC_CHSR & (DMAC_CHSR_ENA0 << ul_num)) ? false : true; +} +//------------------------------------------------------------------------------ +// start RX DMA +static void spiDmaRX(uint8_t* dst, uint16_t count) { + dmac_channel_disable(SPI_DMAC_RX_CH); + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_SADDR = + reinterpret_cast(&SPI0->SPI_RDR); + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DADDR = + reinterpret_cast(dst); + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DSCR = 0; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLA = + count | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLB = + DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | + DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CFG = + DMAC_CFG_SRC_PER(SPI_RX_IDX) | DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | + DMAC_CFG_FIFOCFG_ASAP_CFG; + dmac_channel_enable(SPI_DMAC_RX_CH); +} +//------------------------------------------------------------------------------ +// start TX DMA +static void spiDmaTX(const uint8_t* src, uint16_t count) { + static uint8_t ff = 0XFF; + uint32_t src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING; + if (!src) { + src = &ff; + src_incr = DMAC_CTRLB_SRC_INCR_FIXED; + } + dmac_channel_disable(SPI_DMAC_TX_CH); + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_SADDR = + reinterpret_cast(src); + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = + reinterpret_cast(&SPI0->SPI_TDR); + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR = 0; + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = + count | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; + + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB = + DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | + src_incr | DMAC_CTRLB_DST_INCR_FIXED; + + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = + DMAC_CFG_DST_PER(SPI_TX_IDX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | + DMAC_CFG_FIFOCFG_ALAP_CFG; + + dmac_channel_enable(SPI_DMAC_TX_CH); +} +//------------------------------------------------------------------------------ +// initialize SPI controller +void SdSpiArduinoDriver::activate() { + SPI.beginTransaction(m_spiSettings); + + Spi* pSpi = SPI0; + // Save the divisor + uint32_t scbr = pSpi->SPI_CSR[SPI_CHIP_SEL] & 0XFF00; + // Disable SPI + pSpi->SPI_CR = SPI_CR_SPIDIS; + // reset SPI + pSpi->SPI_CR = SPI_CR_SWRST; + // no mode fault detection, set master mode + pSpi->SPI_MR = SPI_PCS(SPI_CHIP_SEL) | SPI_MR_MODFDIS | SPI_MR_MSTR; + // mode 0, 8-bit, + pSpi->SPI_CSR[SPI_CHIP_SEL] = scbr | SPI_CSR_CSAAT | SPI_CSR_NCPHA; + // enable SPI + pSpi->SPI_CR |= SPI_CR_SPIEN; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + (void)spiConfig; + SPI.begin(); +#if USE_SAM3X_DMAC + pmc_enable_periph_clk(ID_DMAC); + dmac_disable(); + DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED; + dmac_enable(); +#if USE_SAM3X_BUS_MATRIX_FIX + MATRIX->MATRIX_WPMR = 0x4d415400; + MATRIX->MATRIX_MCFG[1] = 1; + MATRIX->MATRIX_MCFG[2] = 1; + MATRIX->MATRIX_SCFG[0] = 0x01000010; + MATRIX->MATRIX_SCFG[1] = 0x01000010; + MATRIX->MATRIX_SCFG[7] = 0x01000010; +#endif // USE_SAM3X_BUS_MATRIX_FIX +#endif // USE_SAM3X_DMAC +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { SPI.endTransaction(); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::end() { SPI.end(); } +//------------------------------------------------------------------------------ +static inline uint8_t spiTransfer(uint8_t b) { + Spi* pSpi = SPI0; + + pSpi->SPI_TDR = b; + while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) { + } + b = pSpi->SPI_RDR; + return b; +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { return spiTransfer(0XFF); } +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { + Spi* pSpi = SPI0; + int rtn = 0; +#if USE_SAM3X_DMAC + // clear overrun error + while (pSpi->SPI_SR & (SPI_SR_OVRES | SPI_SR_RDRF)) { + pSpi->SPI_RDR; + } + spiDmaRX(buf, count); + spiDmaTX(0, count); + + uint32_t m = millis(); + while (!dmac_channel_transfer_done(SPI_DMAC_RX_CH)) { + if ((millis() - m) > SAM3X_DMA_TIMEOUT) { + dmac_channel_disable(SPI_DMAC_RX_CH); + dmac_channel_disable(SPI_DMAC_TX_CH); + rtn = 2; + break; + } + } + if (pSpi->SPI_SR & SPI_SR_OVRES) { + rtn |= 1; + } +#else // USE_SAM3X_DMAC + for (size_t i = 0; i < count; i++) { + pSpi->SPI_TDR = 0XFF; + while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) { + } + buf[i] = pSpi->SPI_RDR; + } +#endif // USE_SAM3X_DMAC + return rtn; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { spiTransfer(data); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { + Spi* pSpi = SPI0; +#if USE_SAM3X_DMAC + spiDmaTX(buf, count); + while (!dmac_channel_transfer_done(SPI_DMAC_TX_CH)) { + } +#else // #if USE_SAM3X_DMAC + while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) { + } + for (size_t i = 0; i < count; i++) { + pSpi->SPI_TDR = buf[i]; + while ((pSpi->SPI_SR & SPI_SR_TDRE) == 0) { + } + } +#endif // #if USE_SAM3X_DMAC + while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) { + } + // leave RDR empty + while (pSpi->SPI_SR & (SPI_SR_OVRES | SPI_SR_RDRF)) { + pSpi->SPI_RDR; + } +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_SAM_DUE) diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiLibDriver.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiLibDriver.h new file mode 100644 index 00000000..6197171a --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiLibDriver.h @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Class using only simple SPI library functions. + */ +#pragma once +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::activate() { + m_spi->beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; +#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else if (spiConfig.csPin == SDCARD_SS_PIN) { + m_spi = &SDCARD_SPI; +#endif // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else { + m_spi = &SPI; + } + if (!(spiConfig.options & USER_SPI_BEGIN)) { + m_spi->begin(); + } +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::end() { m_spi->end(); } +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::deactivate() { m_spi->endTransaction(); } +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive() { return m_spi->transfer(0XFF); } +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { +#if USE_SPI_ARRAY_TRANSFER == 0 + for (size_t i = 0; i < count; i++) { + buf[i] = m_spi->transfer(0XFF); + } +#elif USE_SPI_ARRAY_TRANSFER == 1 + memset(buf, 0XFF, count); + m_spi->transfer(buf, count); +#elif USE_SPI_ARRAY_TRANSFER < 4 + m_spi->transfer(nullptr, buf, count); +#elif USE_SPI_ARRAY_TRANSFER == 4 + uint8_t txTmp[512]; + memset(txTmp, 0XFF, sizeof(txTmp)); + while (count) { + size_t n = count <= sizeof(txTmp) ? count : sizeof(txTmp); + m_spi->transfer(txTmp, buf, n); + buf += n; + count -= n; + } +#else // USE_SPI_ARRAY_TRANSFER == 0 +#error invalid USE_SPI_ARRAY_TRANSFER +#endif // USE_SPI_ARRAY_TRANSFER == 0 + return 0; +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(uint8_t data) { m_spi->transfer(data); } +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { +#if USE_SPI_ARRAY_TRANSFER == 0 + for (size_t i = 0; i < count; i++) { + m_spi->transfer(buf[i]); + } +#elif USE_SPI_ARRAY_TRANSFER == 1 + uint8_t tmp[512]; + while (count > 0) { + size_t n = count <= sizeof(tmp) ? count : sizeof(tmp); + memcpy(tmp, buf, n); + m_spi->transfer(tmp, n); + count -= n; + buf += n; + } +#elif USE_SPI_ARRAY_TRANSFER == 2 + // Some systems do not allow const uint8_t*. + m_spi->transfer(const_cast(buf), nullptr, count); +#elif USE_SPI_ARRAY_TRANSFER < 5 + uint8_t rxTmp[512]; + while (count > 0) { + size_t n = count <= sizeof(rxTmp) ? count : sizeof(rxTmp); + // Some systems do not allow const uint8_t*. + m_spi->transfer(const_cast(buf), rxTmp, n); + buf += n; + count -= n; + } +#else // if USE_SPI_ARRAY_TRANSFER == 0 +#error invalid USE_SPI_ARRAY_TRANSFER +#endif // USE_SPI_ARRAY_TRANSFER == 0 +} diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiSTM32Core.cpp b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiSTM32Core.cpp new file mode 100644 index 00000000..85185fce --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiSTM32Core.cpp @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +// Driver for: https://github.com/stm32duino/Arduino_Core_STM32 +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && defined(STM32_CORE_VERSION) +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::activate() { m_spi->beginTransaction(m_spiSettings); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { m_spi->endTransaction(); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::end() { m_spi->end(); } +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { return m_spi->transfer(0XFF); } +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { + // Must send 0XFF - SD looks at send data for command. + memset(buf, 0XFF, count); + m_spi->transfer(buf, count); + return 0; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { m_spi->transfer(data); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { + // Avoid stack overflow if bad count. This should cause a write error. + if (count > 512) { + return; + } + // Not easy to avoid receive so use tmp RX buffer. + uint8_t rxBuf[512]; + // Discard const - STM32 not const correct. + m_spi->transfer(const_cast(buf), rxBuf, count); +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(STM32_CORE_VERSION) diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiSoftDriver.h b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiSoftDriver.h new file mode 100644 index 00000000..f36a768a --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiSoftDriver.h @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Class for software SPI. + */ +#pragma once +#include "DigitalIO/SoftSPI.h" +/** + * \class SdSpiSoftDriver + * \brief Base class for external soft SPI. + */ +class SdSpiSoftDriver { + public: + /** Activate SPI hardware. */ + void activate() {} + /** Initialize the SPI bus. */ + virtual void begin() = 0; + /** Initialize the SPI bus. + * + * \param[in] spiConfig SD card configuration. + */ + void begin(SdSpiConfig spiConfig) { + (void)spiConfig; + begin(); + } + /** Deactivate SPI hardware. */ + void deactivate() {} + /** deactivate SPI driver. */ + void end() {} + /** Receive a byte. + * + * \return The byte. + */ + virtual uint8_t receive() = 0; + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t count) { + for (size_t i = 0; i < count; i++) { + buf[i] = receive(); + } + return 0; + } + /** Send a byte. + * + * \param[in] data Byte to send + */ + virtual void send(uint8_t data) = 0; + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + void send(const uint8_t* buf, size_t count) { + for (size_t i = 0; i < count; i++) { + send(buf[i]); + } + } + /** Save high speed SPISettings after SD initialization. + * + * \param[in] maxSck Maximum SCK frequency. + */ + void setSckSpeed(uint32_t maxSck) { (void)maxSck; } +}; +//------------------------------------------------------------------------------ +/** + * \class SoftSpiDriver + * \brief Class for external soft SPI. + */ +template +class SoftSpiDriver : public SdSpiSoftDriver { + public: + /** Initialize the SPI bus. */ + void begin() { m_spi.begin(); } + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive() { return m_spi.receive(); } + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data) { m_spi.send(data); } + + private: + SoftSPI m_spi; +}; + +/** Typedef for use of SdSoftSpiDriver */ +typedef SdSpiSoftDriver SdSpiDriver; diff --git a/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiTeensy3.cpp b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiTeensy3.cpp new file mode 100644 index 00000000..3aae0d88 --- /dev/null +++ b/third_party/sdfat/src/SdCard/SdSpiCard/SpiDriver/SdSpiTeensy3.cpp @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && defined(__arm__) && defined(CORE_TEENSY) +#define USE_BLOCK_TRANSFER 1 +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::activate() { m_spi->beginTransaction(m_spiSettings); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; +#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else if (spiConfig.csPin == SDCARD_SS_PIN) { + m_spi = &SDCARD_SPI; + m_spi->setMISO(SDCARD_MISO_PIN); + m_spi->setMOSI(SDCARD_MOSI_PIN); + m_spi->setSCK(SDCARD_SCK_PIN); +#endif // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { m_spi->endTransaction(); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::end() { m_spi->end(); } +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { return m_spi->transfer(0XFF); } +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { +#if USE_BLOCK_TRANSFER + memset(buf, 0XFF, count); + m_spi->transfer(buf, count); +#else // USE_BLOCK_TRANSFER + for (size_t i = 0; i < count; i++) { + buf[i] = m_spi->transfer(0XFF); + } +#endif // USE_BLOCK_TRANSFER + return 0; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { m_spi->transfer(data); } +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { +#if USE_BLOCK_TRANSFER + if (0 < count && count <= 512) { + uint32_t tmp[128]; + memcpy(tmp, buf, count); + m_spi->transfer(tmp, count); + return; + } +#endif // USE_BLOCK_TRANSFER + for (size_t i = 0; i < count; i++) { + m_spi->transfer(buf[i]); + } +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(__arm__) &&defined(CORE_TEENSY) diff --git a/third_party/sdfat/src/SdCard/TeensySdio/CPPLINT.cfg b/third_party/sdfat/src/SdCard/TeensySdio/CPPLINT.cfg new file mode 100644 index 00000000..2cdd1d38 --- /dev/null +++ b/third_party/sdfat/src/SdCard/TeensySdio/CPPLINT.cfg @@ -0,0 +1 @@ +exclude_files=TeensySdioDefs.h diff --git a/third_party/sdfat/src/SdCard/TeensySdio/TeensySdio.cpp b/third_party/sdfat/src/SdCard/TeensySdio/TeensySdio.cpp new file mode 100644 index 00000000..5858f16b --- /dev/null +++ b/third_party/sdfat/src/SdCard/TeensySdio/TeensySdio.cpp @@ -0,0 +1,1178 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__) +#include "../SdCardInfo.h" +#include "TeensySdioCard.h" +#include "TeensySdioDefs.h" +//============================================================================== +// limit of K66 due to errata KINETIS_K_0N65N. +const uint32_t MAX_BLKCNT = 0XFFFF; +//============================================================================== +#define SDHC_PROCTL_DTW_4BIT 0x01 +const uint32_t FIFO_WML = 16; +const uint32_t CMD8_RETRIES = 3; +const uint32_t BUSY_TIMEOUT_MICROS = 1000000; +//============================================================================== +const uint32_t SDHC_IRQSTATEN_MASK = + SDHC_IRQSTATEN_DMAESEN | SDHC_IRQSTATEN_AC12ESEN | SDHC_IRQSTATEN_DEBESEN | + SDHC_IRQSTATEN_DCESEN | SDHC_IRQSTATEN_DTOESEN | SDHC_IRQSTATEN_CIESEN | + SDHC_IRQSTATEN_CEBESEN | SDHC_IRQSTATEN_CCESEN | SDHC_IRQSTATEN_CTOESEN | + SDHC_IRQSTATEN_DINTSEN | SDHC_IRQSTATEN_TCSEN | SDHC_IRQSTATEN_CCSEN; + +const uint32_t SDHC_IRQSTAT_CMD_ERROR = + SDHC_IRQSTAT_CIE | SDHC_IRQSTAT_CEBE | SDHC_IRQSTAT_CCE | SDHC_IRQSTAT_CTOE; + +const uint32_t SDHC_IRQSTAT_DATA_ERROR = SDHC_IRQSTAT_AC12E | + SDHC_IRQSTAT_DEBE | SDHC_IRQSTAT_DCE | + SDHC_IRQSTAT_DTOE; + +const uint32_t SDHC_IRQSTAT_ERROR = + SDHC_IRQSTAT_DMAE | SDHC_IRQSTAT_CMD_ERROR | SDHC_IRQSTAT_DATA_ERROR; + +const uint32_t SDHC_IRQSIGEN_MASK = + SDHC_IRQSIGEN_DMAEIEN | SDHC_IRQSIGEN_AC12EIEN | SDHC_IRQSIGEN_DEBEIEN | + SDHC_IRQSIGEN_DCEIEN | SDHC_IRQSIGEN_DTOEIEN | SDHC_IRQSIGEN_CIEIEN | + SDHC_IRQSIGEN_CEBEIEN | SDHC_IRQSIGEN_CCEIEN | SDHC_IRQSIGEN_CTOEIEN | + SDHC_IRQSIGEN_TCIEN; +//============================================================================== +const uint32_t CMD_RESP_NONE = SDHC_XFERTYP_RSPTYP(0); + +const uint32_t CMD_RESP_R1 = + SDHC_XFERTYP_CICEN | SDHC_XFERTYP_CCCEN | SDHC_XFERTYP_RSPTYP(2); + +const uint32_t CMD_RESP_R1b = + SDHC_XFERTYP_CICEN | SDHC_XFERTYP_CCCEN | SDHC_XFERTYP_RSPTYP(3); + +const uint32_t CMD_RESP_R2 = SDHC_XFERTYP_CCCEN | SDHC_XFERTYP_RSPTYP(1); + +const uint32_t CMD_RESP_R3 = SDHC_XFERTYP_RSPTYP(2); + +const uint32_t CMD_RESP_R6 = CMD_RESP_R1; + +const uint32_t CMD_RESP_R7 = CMD_RESP_R1; + +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +const uint32_t DATA_READ = SDHC_XFERTYP_DTDSEL | SDHC_XFERTYP_DPSEL; + +const uint32_t DATA_READ_DMA = DATA_READ | SDHC_XFERTYP_DMAEN; + +const uint32_t DATA_READ_MULTI_DMA = DATA_READ_DMA | SDHC_XFERTYP_MSBSEL | + SDHC_XFERTYP_AC12EN | SDHC_XFERTYP_BCEN; + +const uint32_t DATA_READ_MULTI_PGM = + DATA_READ | SDHC_XFERTYP_MSBSEL | SDHC_XFERTYP_BCEN; + +const uint32_t DATA_WRITE_DMA = SDHC_XFERTYP_DPSEL | SDHC_XFERTYP_DMAEN; + +const uint32_t DATA_WRITE_MULTI_DMA = DATA_WRITE_DMA | SDHC_XFERTYP_MSBSEL | + SDHC_XFERTYP_AC12EN | SDHC_XFERTYP_BCEN; + +const uint32_t DATA_WRITE_MULTI_PGM = + SDHC_XFERTYP_DPSEL | SDHC_XFERTYP_MSBSEL | SDHC_XFERTYP_BCEN; + +#elif defined(__IMXRT1062__) +// Use low bits for SDHC_MIX_CTRL since bits 15-0 of SDHC_XFERTYP are reserved. +const uint32_t SDHC_MIX_CTRL_MASK = + SDHC_MIX_CTRL_DMAEN | SDHC_MIX_CTRL_BCEN | SDHC_MIX_CTRL_AC12EN | + SDHC_MIX_CTRL_DDR_EN | SDHC_MIX_CTRL_DTDSEL | SDHC_MIX_CTRL_MSBSEL | + SDHC_MIX_CTRL_NIBBLE_POS | SDHC_MIX_CTRL_AC23EN; + +const uint32_t DATA_READ = SDHC_MIX_CTRL_DTDSEL | SDHC_XFERTYP_DPSEL; + +const uint32_t DATA_READ_DMA = DATA_READ | SDHC_MIX_CTRL_DMAEN; + +const uint32_t DATA_READ_MULTI_DMA = DATA_READ_DMA | SDHC_MIX_CTRL_MSBSEL | + SDHC_MIX_CTRL_AC12EN | SDHC_MIX_CTRL_BCEN; + +const uint32_t DATA_READ_MULTI_PGM = DATA_READ | SDHC_MIX_CTRL_MSBSEL; + +const uint32_t DATA_WRITE_DMA = SDHC_XFERTYP_DPSEL | SDHC_MIX_CTRL_DMAEN; + +const uint32_t DATA_WRITE_MULTI_DMA = DATA_WRITE_DMA | SDHC_MIX_CTRL_MSBSEL | + SDHC_MIX_CTRL_AC12EN | SDHC_MIX_CTRL_BCEN; + +const uint32_t DATA_WRITE_MULTI_PGM = SDHC_XFERTYP_DPSEL | SDHC_MIX_CTRL_MSBSEL; + +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + +const uint32_t ACMD6_XFERTYP = SDHC_XFERTYP_CMDINX(ACMD6) | CMD_RESP_R1; + +const uint32_t ACMD13_XFERTYP = + SDHC_XFERTYP_CMDINX(ACMD13) | CMD_RESP_R1 | DATA_READ_DMA; + +const uint32_t ACMD41_XFERTYP = SDHC_XFERTYP_CMDINX(ACMD41) | CMD_RESP_R3; + +const uint32_t ACMD51_XFERTYP = + SDHC_XFERTYP_CMDINX(ACMD51) | CMD_RESP_R1 | DATA_READ_DMA; + +const uint32_t CMD0_XFERTYP = SDHC_XFERTYP_CMDINX(CMD0) | CMD_RESP_NONE; + +const uint32_t CMD2_XFERTYP = SDHC_XFERTYP_CMDINX(CMD2) | CMD_RESP_R2; + +const uint32_t CMD3_XFERTYP = SDHC_XFERTYP_CMDINX(CMD3) | CMD_RESP_R6; + +const uint32_t CMD6_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD6) | CMD_RESP_R1 | DATA_READ_DMA; + +const uint32_t CMD7_XFERTYP = SDHC_XFERTYP_CMDINX(CMD7) | CMD_RESP_R1b; + +const uint32_t CMD8_XFERTYP = SDHC_XFERTYP_CMDINX(CMD8) | CMD_RESP_R7; + +const uint32_t CMD9_XFERTYP = SDHC_XFERTYP_CMDINX(CMD9) | CMD_RESP_R2; + +const uint32_t CMD10_XFERTYP = SDHC_XFERTYP_CMDINX(CMD10) | CMD_RESP_R2; + +const uint32_t CMD11_XFERTYP = SDHC_XFERTYP_CMDINX(CMD11) | CMD_RESP_R1; + +const uint32_t CMD12_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD12) | CMD_RESP_R1b | SDHC_XFERTYP_CMDTYP(3); + +const uint32_t CMD13_XFERTYP = SDHC_XFERTYP_CMDINX(CMD13) | CMD_RESP_R1; + +const uint32_t CMD17_DMA_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD17) | CMD_RESP_R1 | DATA_READ_DMA; + +const uint32_t CMD18_DMA_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD18) | CMD_RESP_R1 | DATA_READ_MULTI_DMA; + +const uint32_t CMD18_PGM_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD18) | CMD_RESP_R1 | DATA_READ_MULTI_PGM; + +const uint32_t CMD24_DMA_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD24) | CMD_RESP_R1 | DATA_WRITE_DMA; + +const uint32_t CMD25_DMA_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD25) | CMD_RESP_R1 | DATA_WRITE_MULTI_DMA; + +const uint32_t CMD25_PGM_XFERTYP = + SDHC_XFERTYP_CMDINX(CMD25) | CMD_RESP_R1 | DATA_WRITE_MULTI_PGM; + +const uint32_t CMD32_XFERTYP = SDHC_XFERTYP_CMDINX(CMD32) | CMD_RESP_R1; + +const uint32_t CMD33_XFERTYP = SDHC_XFERTYP_CMDINX(CMD33) | CMD_RESP_R1; + +const uint32_t CMD38_XFERTYP = SDHC_XFERTYP_CMDINX(CMD38) | CMD_RESP_R1b; + +const uint32_t CMD55_XFERTYP = SDHC_XFERTYP_CMDINX(CMD55) | CMD_RESP_R1; + +//============================================================================== +static bool cardCommand(uint32_t xfertyp, uint32_t arg); +static void enableGPIO(bool enable); +static void enableDmaIrs(); +static void initSDHC(); +static bool isBusyCMD13(); +static bool isBusyCommandComplete(); +static bool isBusyCommandInhibit(); +static bool readReg16(uint32_t xfertyp, void* data); +static void setSdclk(uint32_t kHzMax); +static bool yieldTimeout(bool (*fcn)()); +static bool waitDmaStatus(); +static bool waitTimeout(bool (*fcn)()); +//------------------------------------------------------------------------------ +static bool (*m_busyFcn)() = 0; +static bool m_initDone = false; +static bool m_version2 = false; +static bool m_highCapacity = false; +static bool m_transferActive = false; +static bool m_useDma = false; +static uint8_t m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED; +static uint32_t m_errorLine = 0; +static uint32_t m_rca; +static volatile bool m_dmaBusy = false; +static volatile uint32_t m_irqstat; +static uint32_t m_sdClkKhz = 0; +static uint32_t m_ocr; +static cid_t m_cid; +static csd_t m_csd; +static scr_t m_scr; +static sds_t m_sds; +//============================================================================== +#define DBG_TRACE \ + Serial.print("TRACE."); \ + Serial.println(__LINE__); \ + delay(200); +#define USE_DEBUG_MODE 0 +#if USE_DEBUG_MODE +#define DBG_IRQSTAT() \ + if (SDHC_IRQSTAT) { \ + Serial.print(__LINE__); \ + Serial.print(" IRQSTAT "); \ + Serial.println(SDHC_IRQSTAT, HEX); \ + } +static void printRegs(uint32_t line) { + uint32_t blkattr = SDHC_BLKATTR; + uint32_t xfertyp = SDHC_XFERTYP; + uint32_t prsstat = SDHC_PRSSTAT; + uint32_t proctl = SDHC_PROCTL; + uint32_t irqstat = SDHC_IRQSTAT; + Serial.print("\nLINE: "); + Serial.println(line); + Serial.print("BLKATTR "); + Serial.println(blkattr, HEX); + Serial.print("XFERTYP "); + Serial.print(xfertyp, HEX); + Serial.print(" CMD"); + Serial.print(xfertyp >> 24); + Serial.print(" TYP"); + Serial.print((xfertyp >> 2) & 3); + if (xfertyp & SDHC_XFERTYP_DPSEL) { + Serial.print(" DPSEL"); + } + Serial.println(); + Serial.print("PRSSTAT "); + Serial.print(prsstat, HEX); + if (prsstat & SDHC_PRSSTAT_BREN) { + Serial.print(" BREN"); + } + if (prsstat & SDHC_PRSSTAT_BWEN) { + Serial.print(" BWEN"); + } + if (prsstat & SDHC_PRSSTAT_RTA) { + Serial.print(" RTA"); + } + if (prsstat & SDHC_PRSSTAT_WTA) { + Serial.print(" WTA"); + } + if (prsstat & SDHC_PRSSTAT_SDOFF) { + Serial.print(" SDOFF"); + } + if (prsstat & SDHC_PRSSTAT_PEROFF) { + Serial.print(" PEROFF"); + } + if (prsstat & SDHC_PRSSTAT_HCKOFF) { + Serial.print(" HCKOFF"); + } + if (prsstat & SDHC_PRSSTAT_IPGOFF) { + Serial.print(" IPGOFF"); + } + if (prsstat & SDHC_PRSSTAT_SDSTB) { + Serial.print(" SDSTB"); + } + if (prsstat & SDHC_PRSSTAT_DLA) { + Serial.print(" DLA"); + } + if (prsstat & SDHC_PRSSTAT_CDIHB) { + Serial.print(" CDIHB"); + } + if (prsstat & SDHC_PRSSTAT_CIHB) { + Serial.print(" CIHB"); + } + Serial.println(); + Serial.print("PROCTL "); + Serial.print(proctl, HEX); + if (proctl & SDHC_PROCTL_SABGREQ) Serial.print(" SABGREQ"); + Serial.print(" EMODE"); + Serial.print((proctl >> 4) & 3); + Serial.print(" DWT"); + Serial.print((proctl >> 1) & 3); + Serial.println(); + Serial.print("IRQSTAT "); + Serial.print(irqstat, HEX); + if (irqstat & SDHC_IRQSTAT_BGE) { + Serial.print(" BGE"); + } + if (irqstat & SDHC_IRQSTAT_TC) { + Serial.print(" TC"); + } + if (irqstat & SDHC_IRQSTAT_CC) { + Serial.print(" CC"); + } + Serial.print("\nm_irqstat "); + Serial.println(m_irqstat, HEX); +} +#else // USE_DEBUG_MODE +#define DBG_IRQSTAT() +#endif // USE_DEBUG_MODE +//============================================================================== +// Error function and macro. +#define sdError(code) setSdErrorCode(code, __LINE__) +inline bool setSdErrorCode(uint8_t code, uint32_t line) { + m_errorCode = code; + m_errorLine = line; +#if USE_DEBUG_MODE + printRegs(line); +#endif // USE_DEBUG_MODE + return false; +} +//============================================================================== +// ISR +static void sdIrs() { + SDHC_IRQSIGEN = 0; + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; +#if defined(__IMXRT1062__) + SDHC_MIX_CTRL &= ~(SDHC_MIX_CTRL_AC23EN | SDHC_MIX_CTRL_DMAEN); +#endif + m_dmaBusy = false; +} +//============================================================================== +// GPIO and clock functions. +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +//------------------------------------------------------------------------------ +static void enableGPIO(bool enable) { + const uint32_t PORT_CLK = PORT_PCR_MUX(4) | PORT_PCR_DSE; + const uint32_t PORT_CMD_DATA = PORT_CLK | PORT_PCR_PE | PORT_PCR_PS; + const uint32_t PORT_PUP = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; + + PORTE_PCR0 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D1 + PORTE_PCR1 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D0 + PORTE_PCR2 = enable ? PORT_CLK : PORT_PUP; // SDHC_CLK + PORTE_PCR3 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_CMD + PORTE_PCR4 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D3 + PORTE_PCR5 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D2 +} +//------------------------------------------------------------------------------ +static void initClock() { +#ifdef HAS_KINETIS_MPU + // Allow SDHC Bus Master access. + MPU_RGDAAC0 |= 0x0C000000; +#endif // HAS_KINETIS_MPU + // Enable SDHC clock. + SIM_SCGC3 |= SIM_SCGC3_SDHC; +} +static uint32_t baseClock() { return F_CPU; } + +#elif defined(__IMXRT1062__) +//------------------------------------------------------------------------------ +static void gpioMux(uint8_t mode) { + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_04 = mode; // DAT2 + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_05 = mode; // DAT3 + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_00 = mode; // CMD + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_01 = mode; // CLK + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_02 = mode; // DAT0 + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_03 = mode; // DAT1 +} +//------------------------------------------------------------------------------ +// add speed strength args? +static void enableGPIO(bool enable) { + const uint32_t CLOCK_MASK = IOMUXC_SW_PAD_CTL_PAD_PKE | +#if defined(ARDUINO_TEENSY41) + IOMUXC_SW_PAD_CTL_PAD_DSE(7) | +#else // defined(ARDUINO_TEENSY41) + IOMUXC_SW_PAD_CTL_PAD_DSE(4) | ///// WHG +#endif // defined(ARDUINO_TEENSY41) + IOMUXC_SW_PAD_CTL_PAD_SPEED(2); + + const uint32_t DATA_MASK = + CLOCK_MASK | IOMUXC_SW_PAD_CTL_PAD_PUE | IOMUXC_SW_PAD_CTL_PAD_PUS(1); + if (enable) { + gpioMux(0); + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_04 = DATA_MASK; // DAT2 + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_05 = DATA_MASK; // DAT3 + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_00 = DATA_MASK; // CMD + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_01 = CLOCK_MASK; // CLK + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_02 = DATA_MASK; // DAT0 + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_03 = DATA_MASK; // DAT1 + } else { + gpioMux(5); + } +} +//------------------------------------------------------------------------------ +static void initClock() { + /* set PDF_528 PLL2PFD0 */ + CCM_ANALOG_PFD_528 |= (1 << 7); + CCM_ANALOG_PFD_528 &= ~(0x3F << 0); + CCM_ANALOG_PFD_528 |= ((24) & 0x3F << 0); // 12 - 35 + CCM_ANALOG_PFD_528 &= ~(1 << 7); + + /* Enable USDHC clock. */ + CCM_CCGR6 |= CCM_CCGR6_USDHC1(CCM_CCGR_ON); + CCM_CSCDR1 &= ~(CCM_CSCDR1_USDHC1_CLK_PODF_MASK); + CCM_CSCMR1 |= CCM_CSCMR1_USDHC1_CLK_SEL; // PLL2PFD0 + // CCM_CSCDR1 |= CCM_CSCDR1_USDHC1_CLK_PODF((7)); / &0x7 WHG + CCM_CSCDR1 |= CCM_CSCDR1_USDHC1_CLK_PODF((1)); +} +//------------------------------------------------------------------------------ +static uint32_t baseClock() { + uint32_t divider = ((CCM_CSCDR1 >> 11) & 0x7) + 1; + return (528000000U * 3) / ((CCM_ANALOG_PFD_528 & 0x3F) / 6) / divider; +} +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +//============================================================================== +// Static functions. +static bool cardAcmd(uint32_t rca, uint32_t xfertyp, uint32_t arg) { + return cardCommand(CMD55_XFERTYP, rca) && cardCommand(xfertyp, arg); +} +//------------------------------------------------------------------------------ +static bool cardCommand(uint32_t xfertyp, uint32_t arg) { + DBG_IRQSTAT(); + if (waitTimeout(isBusyCommandInhibit)) { + return false; // Caller will set errorCode. + } + SDHC_CMDARG = arg; +#if defined(__IMXRT1062__) + // Set MIX_CTRL if data transfer. + if (xfertyp & SDHC_XFERTYP_DPSEL) { + SDHC_MIX_CTRL &= ~SDHC_MIX_CTRL_MASK; + SDHC_MIX_CTRL |= xfertyp & SDHC_MIX_CTRL_MASK; + } + xfertyp &= ~SDHC_MIX_CTRL_MASK; +#endif // defined(__IMXRT1062__) + SDHC_XFERTYP = xfertyp; + if (waitTimeout(isBusyCommandComplete)) { + return false; // Caller will set errorCode. + } + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; + + return (m_irqstat & SDHC_IRQSTAT_CC) && !(m_irqstat & SDHC_IRQSTAT_CMD_ERROR); +} +//------------------------------------------------------------------------------ +static bool cardACMD13(sds_t* scr) { + // ACMD13 returns 64 bytes. + if (waitTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + enableDmaIrs(); + SDHC_DSADDR = reinterpret_cast(scr); + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(1) | SDHC_BLKATTR_BLKSIZE(64); + SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK; + if (!cardAcmd(m_rca, ACMD13_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_ACMD13); + } + if (!waitDmaStatus()) { + return sdError(SD_CARD_ERROR_DMA); + } + return true; +} +//------------------------------------------------------------------------------ +static bool cardACMD51(scr_t* scr) { + // ACMD51 returns 8 bytes. + if (waitTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + enableDmaIrs(); + SDHC_DSADDR = reinterpret_cast(scr); + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(1) | SDHC_BLKATTR_BLKSIZE(8); + SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK; + if (!cardAcmd(m_rca, ACMD51_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_ACMD51); + } + if (!waitDmaStatus()) { + return sdError(SD_CARD_ERROR_DMA); + } + return true; +} +//------------------------------------------------------------------------------ +static void enableDmaIrs() { + m_dmaBusy = true; + m_irqstat = 0; +} +//------------------------------------------------------------------------------ +static void initSDHC() { + initClock(); + + // Disable GPIO clock. + enableGPIO(false); + +#if defined(__IMXRT1062__) + SDHC_MIX_CTRL |= 0x80000000; +#endif // (__IMXRT1062__) + + // Reset SDHC. Use default Water Mark Level of 16. + SDHC_SYSCTL |= SDHC_SYSCTL_RSTA | SDHC_SYSCTL_SDCLKFS(0x80); + + while (SDHC_SYSCTL & SDHC_SYSCTL_RSTA) { + } + + // Set initial SCK rate. + setSdclk(SD_MAX_INIT_RATE_KHZ); + + enableGPIO(true); + + // Enable desired IRQSTAT bits. + SDHC_IRQSTATEN = SDHC_IRQSTATEN_MASK; + + attachInterruptVector(IRQ_SDHC, sdIrs); + NVIC_SET_PRIORITY(IRQ_SDHC, 6 * 16); + NVIC_ENABLE_IRQ(IRQ_SDHC); + + // Send 80 clocks to card. + SDHC_SYSCTL |= SDHC_SYSCTL_INITA; + while (SDHC_SYSCTL & SDHC_SYSCTL_INITA) { + } +} +//------------------------------------------------------------------------------ +static uint32_t statusCMD13() { + return cardCommand(CMD13_XFERTYP, m_rca) ? SDHC_CMDRSP0 : 0; +} +//------------------------------------------------------------------------------ +static bool isBusyCMD13() { + return !(statusCMD13() & CARD_STATUS_READY_FOR_DATA); +} +//------------------------------------------------------------------------------ +static bool isBusyCommandComplete() { + return !(SDHC_IRQSTAT & (SDHC_IRQSTAT_CC | SDHC_IRQSTAT_CMD_ERROR)); +} +//------------------------------------------------------------------------------ +static bool isBusyCommandInhibit() { return SDHC_PRSSTAT & SDHC_PRSSTAT_CIHB; } +//------------------------------------------------------------------------------ +static bool isBusyDat() { return (SDHC_PRSSTAT & (1 << 24)) ? false : true; } +//------------------------------------------------------------------------------ +static bool isBusyDMA() { return m_dmaBusy; } +//------------------------------------------------------------------------------ +static bool isBusyFifoRead() { return !(SDHC_PRSSTAT & SDHC_PRSSTAT_BREN); } +//------------------------------------------------------------------------------ +static bool isBusyFifoWrite() { return !(SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN); } +//------------------------------------------------------------------------------ +static bool isBusyTransferComplete() { + return !(SDHC_IRQSTAT & (SDHC_IRQSTAT_TC | SDHC_IRQSTAT_ERROR)); +} +//------------------------------------------------------------------------------ +static bool rdWrSectors(uint32_t xfertyp, Sector_t sector, uint8_t* buf, + size_t n) { + if ((3 & reinterpret_cast(buf)) || n == 0) { + return sdError(SD_CARD_ERROR_DMA); + } + if (yieldTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + enableDmaIrs(); + SDHC_DSADDR = reinterpret_cast(buf); + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(n) | SDHC_BLKATTR_BLKSIZE(512); + SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK; + if (!cardCommand(xfertyp, m_highCapacity ? sector : 512 * sector)) { + return false; + } + return waitDmaStatus(); +} +//------------------------------------------------------------------------------ +// Read 16 byte CID or CSD register. +static bool readReg16(uint32_t xfertyp, void* data) { + uint8_t* d = reinterpret_cast(data); + if (!cardCommand(xfertyp, m_rca)) { + return false; // Caller will set errorCode. + } + uint32_t const sr[] = {SDHC_CMDRSP0, SDHC_CMDRSP1, SDHC_CMDRSP2, + SDHC_CMDRSP3}; + for (int i = 0; i < 15; i++) { + d[14 - i] = sr[i / 4] >> 8 * (i % 4); + } + d[15] = 0; + return true; +} +//------------------------------------------------------------------------------ +static void setSdclk(uint32_t kHzMax) { + const uint32_t DVS_LIMIT = 0X10; + const uint32_t SDCLKFS_LIMIT = 0X100; + uint32_t dvs = 1; + uint32_t sdclkfs = 1; + uint32_t maxSdclk = 1000 * kHzMax; + uint32_t base = baseClock(); + + while ((base / (sdclkfs * DVS_LIMIT) > maxSdclk) && + (sdclkfs < SDCLKFS_LIMIT)) { + sdclkfs <<= 1; + } + while ((base / (sdclkfs * dvs) > maxSdclk) && (dvs < DVS_LIMIT)) { + dvs++; + } + m_sdClkKhz = base / (1000 * sdclkfs * dvs); + sdclkfs >>= 1; + dvs--; +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + // Disable SDHC clock. + SDHC_SYSCTL &= ~SDHC_SYSCTL_SDCLKEN; +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + + // Change dividers. + uint32_t sysctl = + SDHC_SYSCTL & ~(SDHC_SYSCTL_DTOCV_MASK | SDHC_SYSCTL_DVS_MASK | + SDHC_SYSCTL_SDCLKFS_MASK); + + SDHC_SYSCTL = sysctl | SDHC_SYSCTL_DTOCV(0x0E) | SDHC_SYSCTL_DVS(dvs) | + SDHC_SYSCTL_SDCLKFS(sdclkfs); + + // Wait until the SDHC clock is stable. + while (!(SDHC_PRSSTAT & SDHC_PRSSTAT_SDSTB)) { + } + +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + // Enable the SDHC clock. + SDHC_SYSCTL |= SDHC_SYSCTL_SDCLKEN; +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +} +//------------------------------------------------------------------------------ +static bool transferStop() { + // This fix allows CDIHB to be cleared in Tennsy 3.x without a reset. + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + if (!cardCommand(CMD12_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD12); + } + if (yieldTimeout(isBusyDat)) { + return sdError(SD_CARD_ERROR_CMD13); + } + if (SDHC_PRSSTAT & SDHC_PRSSTAT_CDIHB) { + // This should not happen after above fix. + // Save registers before reset DAT lines. + uint32_t irqsststen = SDHC_IRQSTATEN; + uint32_t proctl = SDHC_PROCTL & ~SDHC_PROCTL_SABGREQ; + // Do reset to clear CDIHB. Should be a better way! + SDHC_SYSCTL |= SDHC_SYSCTL_RSTD; + // Restore registers. + SDHC_IRQSTATEN = irqsststen; + SDHC_PROCTL = proctl; + } + return true; +} +//------------------------------------------------------------------------------ +// Return true if timeout occurs. +static bool yieldTimeout(bool (*fcn)()) { + m_busyFcn = fcn; + uint32_t m = micros(); + while (fcn()) { + if ((micros() - m) > BUSY_TIMEOUT_MICROS) { + m_busyFcn = 0; + return true; + } + yield(); + } + m_busyFcn = 0; + return false; // Caller will set errorCode. +} +//------------------------------------------------------------------------------ +static bool waitDmaStatus() { + if (yieldTimeout(isBusyDMA)) { + return false; // Caller will set errorCode. + } + return (m_irqstat & SDHC_IRQSTAT_TC) && !(m_irqstat & SDHC_IRQSTAT_ERROR); +} +//------------------------------------------------------------------------------ +// Return true if timeout occurs. +static bool waitTimeout(bool (*fcn)()) { + uint32_t m = micros(); + while (fcn()) { + if ((micros() - m) > BUSY_TIMEOUT_MICROS) { + return true; + } + } + return false; // Caller will set errorCode. +} +//------------------------------------------------------------------------------ +static bool waitTransferComplete() { + if (!m_transferActive) { + return true; + } + bool timeOut = waitTimeout(isBusyTransferComplete); + m_transferActive = false; + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; + if (timeOut || (m_irqstat & SDHC_IRQSTAT_ERROR)) { + return sdError(SD_CARD_ERROR_TRANSFER_COMPLETE); + } + return true; +} +//============================================================================== +// Start of TeensySdioCard member functions. +//============================================================================== +bool TeensySdioCard::begin(TeensySdioConfig sdioConfig) { + uint32_t kHzClk; + uint32_t arg; + m_useDma = sdioConfig.useDma(); + m_curState = IDLE_STATE; + m_initDone = false; + m_errorCode = SD_CARD_ERROR_NONE; + m_highCapacity = false; + m_version2 = false; + + // initialize controller. + initSDHC(); + if (!cardCommand(CMD0_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD0); + } + // Try several times for case of reset delay. + for (uint32_t i = 0; i < CMD8_RETRIES; i++) { + if (cardCommand(CMD8_XFERTYP, 0X1AA)) { + if (SDHC_CMDRSP0 != 0X1AA) { + return sdError(SD_CARD_ERROR_CMD8); + } + m_version2 = true; + break; + } + SDHC_SYSCTL |= SDHC_SYSCTL_RSTA; + while (SDHC_SYSCTL & SDHC_SYSCTL_RSTA) { + } + } + // Must support 3.2-3.4 Volts + arg = m_version2 ? 0X40300000 : 0x00300000; + int m = micros(); + do { + if (!cardAcmd(0, ACMD41_XFERTYP, arg) || + ((micros() - m) > BUSY_TIMEOUT_MICROS)) { + return sdError(SD_CARD_ERROR_ACMD41); + } + } while ((SDHC_CMDRSP0 & 0x80000000) == 0); + m_ocr = SDHC_CMDRSP0; + if (SDHC_CMDRSP0 & 0x40000000) { + // Is high capacity. + m_highCapacity = true; + } + if (!cardCommand(CMD2_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD2); + } + if (!cardCommand(CMD3_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD3); + } + m_rca = SDHC_CMDRSP0 & 0xFFFF0000; + + if (!readReg16(CMD9_XFERTYP, &m_csd)) { + return sdError(SD_CARD_ERROR_CMD9); + } + if (!readReg16(CMD10_XFERTYP, &m_cid)) { + return sdError(SD_CARD_ERROR_CMD10); + } + if (!cardCommand(CMD7_XFERTYP, m_rca)) { + return sdError(SD_CARD_ERROR_CMD7); + } + // Set card to bus width four. + if (!cardAcmd(m_rca, ACMD6_XFERTYP, 2)) { + return sdError(SD_CARD_ERROR_ACMD6); + } + // Set SDHC to bus width four. + SDHC_PROCTL &= ~SDHC_PROCTL_DTW_MASK; + SDHC_PROCTL |= SDHC_PROCTL_DTW(SDHC_PROCTL_DTW_4BIT); + + SDHC_WML = SDHC_WML_RDWML(FIFO_WML) | SDHC_WML_WRWML(FIFO_WML); + + if (!cardACMD51(&m_scr)) { + return false; + } + if (!cardACMD13(&m_sds)) { + return false; + } + // Determine if High Speed mode is supported and set frequency. + // Check status[16] for error 0XF or status[16] for new mode 0X1. + uint8_t stat[64]; + kHzClk = 25000; + if (m_scr.sdSpec() > 0) { + // card is 1.10 or greater - must support CMD6 + if (!cardCMD6(0X00FFFFFF, stat)) { + return false; + } + if (2 & stat[13]) { + // Card supports High Speed mode - switch mode. + if (!cardCMD6(0X80FFFFF1, stat)) { + return false; + } + if ((stat[16] & 0XF) == 1) { + kHzClk = 50000; + } else { + return sdError(SD_CARD_ERROR_CMD6); + } + } + } + // Disable GPIO. + enableGPIO(false); + + // Set the SDHC SCK frequency. + setSdclk(kHzClk); + + // Enable GPIO. + enableGPIO(true); + m_initDone = true; + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::cardCMD6(uint32_t arg, uint8_t* status) { + // CMD6 returns 64 bytes. + if (waitTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + enableDmaIrs(); + SDHC_DSADDR = reinterpret_cast(status); + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(1) | SDHC_BLKATTR_BLKSIZE(64); + SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK; + if (!cardCommand(CMD6_XFERTYP, arg)) { + return sdError(SD_CARD_ERROR_CMD6); + } + if (!waitDmaStatus()) { + return sdError(SD_CARD_ERROR_DMA); + } + return true; +} +//------------------------------------------------------------------------------ +void TeensySdioCard::end() { + // to do +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::erase(Sector_t firstSector, Sector_t lastSector) { + if (m_curState != IDLE_STATE && !syncDevice()) { + return false; + } + // check for single sector erase + if (!m_csd.eraseSingleBlock()) { + // erase size mask + uint8_t m = m_csd.eraseSize() - 1; + if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) { + // error card can't erase specified area + return sdError(SD_CARD_ERROR_ERASE_SINGLE_SECTOR); + } + } + if (!m_highCapacity) { + firstSector <<= 9; + lastSector <<= 9; + } + if (!cardCommand(CMD32_XFERTYP, firstSector)) { + return sdError(SD_CARD_ERROR_CMD32); + } + if (!cardCommand(CMD33_XFERTYP, lastSector)) { + return sdError(SD_CARD_ERROR_CMD33); + } + if (!cardCommand(CMD38_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD38); + } + if (waitTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_ERASE_TIMEOUT); + } + return true; +} +//------------------------------------------------------------------------------ +uint8_t TeensySdioCard::errorCode() const { return m_errorCode; } +//------------------------------------------------------------------------------ +uint32_t TeensySdioCard::errorData() const { return m_irqstat; } +//------------------------------------------------------------------------------ +uint32_t TeensySdioCard::errorLine() const { return m_errorLine; } +//------------------------------------------------------------------------------ +bool TeensySdioCard::isBusy() { + if (m_useDma) { + return m_busyFcn ? m_busyFcn() : m_initDone && isBusyCMD13(); + } else { + if (m_transferActive) { + if (isBusyTransferComplete()) { + return true; + } +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + if ((SDHC_BLKATTR & 0XFFFF0000) != 0) { + return false; + } + m_transferActive = false; + stopTransmission(false); + return true; +#else // defined(__MK64FX512__) || defined(__MK66FX1M0__) + return false; +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + } + // Use DAT0 low as busy. + return (SDHC_PRSSTAT & (1 << 24)) ? false : true; + } +} +//------------------------------------------------------------------------------ +uint32_t TeensySdioCard::kHzSdClk() { return m_sdClkKhz; } +//------------------------------------------------------------------------------ +bool TeensySdioCard::readCID(cid_t* cid) { + memcpy(cid, &m_cid, sizeof(cid_t)); + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readCSD(csd_t* csd) { + memcpy(csd, &m_csd, sizeof(csd_t)); + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readData(uint8_t* dst) { + DBG_IRQSTAT(); + uint32_t* p32 = reinterpret_cast(dst); + + if (!(SDHC_PRSSTAT & SDHC_PRSSTAT_RTA)) { + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + noInterrupts(); + SDHC_PROCTL |= SDHC_PROCTL_CREQ; + SDHC_PROCTL |= SDHC_PROCTL_SABGREQ; + interrupts(); + } + if (waitTimeout(isBusyFifoRead)) { + return sdError(SD_CARD_ERROR_READ_FIFO); + } + for (uint32_t iw = 0; iw < 512 / (4 * FIFO_WML); iw++) { + while (0 == (SDHC_PRSSTAT & SDHC_PRSSTAT_BREN)) { + } + for (uint32_t i = 0; i < FIFO_WML; i++) { + p32[i] = SDHC_DATPORT; + } + p32 += FIFO_WML; + } + if (waitTimeout(isBusyTransferComplete)) { + return sdError(SD_CARD_ERROR_READ_TIMEOUT); + } + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; + return (m_irqstat & SDHC_IRQSTAT_TC) && !(m_irqstat & SDHC_IRQSTAT_ERROR); +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readOCR(uint32_t* ocr) { + *ocr = m_ocr; + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readSCR(scr_t* scr) { + memcpy(scr, &m_scr, sizeof(scr_t)); + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readSDS(sds_t* sds) { + memcpy(sds, &m_sds, sizeof(sds_t)); + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readSector(Sector_t sector, uint8_t* dst) { + if (m_useDma) { + if (reinterpret_cast(dst) & 3) { + // Not aligned. + uint8_t tmp[512]; + if (!rdWrSectors(CMD17_DMA_XFERTYP, sector, tmp, 1)) { + return sdError(SD_CARD_ERROR_CMD17); + } + memcpy(dst, tmp, 512); + } else { + if (!rdWrSectors(CMD17_DMA_XFERTYP, sector, dst, 1)) { + return sdError(SD_CARD_ERROR_CMD17); + } + } + } else { + if (!waitTransferComplete()) { + return false; + } + if (m_curState != READ_STATE || sector != m_curSector) { + if (!syncDevice()) { + return false; + } + if (!readStart(sector)) { + return false; + } + m_curSector = sector; + m_curState = READ_STATE; + } + if (!readData(dst)) { + return false; + } +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + if ((SDHC_BLKATTR & 0XFFFF0000) == 0) { + if (!syncDevice()) { + return false; + } + } +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + m_curSector++; + } + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readSectors(Sector_t sector, uint8_t* dst, size_t n) { + if (m_useDma) { + if (reinterpret_cast(dst) & 3) { + for (size_t i = 0; i < n; i++, sector++, dst += 512) { + if (!readSector(sector, dst)) { + return false; // readSector will set errorCode. + } + } + return true; + } + if (!rdWrSectors(CMD18_DMA_XFERTYP, sector, dst, n)) { + return sdError(SD_CARD_ERROR_CMD18); + } + } else { + for (size_t i = 0; i < n; i++) { + if (!readSector(sector + i, dst + i * 512UL)) { + return false; + } + } + } + return true; +} +//------------------------------------------------------------------------------ +// SDHC will do Auto CMD12 after count sectors. +bool TeensySdioCard::readStart(Sector_t sector) { + DBG_IRQSTAT(); + if (yieldTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + SDHC_PROCTL |= SDHC_PROCTL_SABGREQ; +#if defined(__IMXRT1062__) + // Infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKSIZE(512); +#else // defined(__IMXRT1062__) + // Errata - can't do infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(MAX_BLKCNT) | SDHC_BLKATTR_BLKSIZE(512); +#endif // defined(__IMXRT1062__) + + if (!cardCommand(CMD18_PGM_XFERTYP, m_highCapacity ? sector : 512 * sector)) { + return sdError(SD_CARD_ERROR_CMD18); + } + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::readStop() { return transferStop(); } +//------------------------------------------------------------------------------ +uint32_t TeensySdioCard::sectorCount() { return m_csd.capacity(); } +//------------------------------------------------------------------------------ +uint32_t TeensySdioCard::status() { return statusCMD13(); } +//------------------------------------------------------------------------------ +bool TeensySdioCard::stopTransmission(bool blocking) { + m_curState = IDLE_STATE; + // This fix allows CDIHB to be cleared in Tennsy 3.x without a reset. + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + if (!cardCommand(CMD12_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD12); + } + if (blocking) { + if (yieldTimeout(isBusyDat)) { + return sdError(SD_CARD_ERROR_CMD13); + } + } + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::syncDevice() { + if (!waitTransferComplete()) { + return false; + } + if (m_curState != IDLE_STATE) { + return stopTransmission(true); + } + return true; +} +//------------------------------------------------------------------------------ +uint8_t TeensySdioCard::type() const { + return !m_initDone ? 0 + : !m_version2 ? SD_CARD_TYPE_SD1 + : !m_highCapacity ? SD_CARD_TYPE_SD2 + : SD_CARD_TYPE_SDHC; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::writeData(const uint8_t* src) { + DBG_IRQSTAT(); + if (!waitTransferComplete()) { + return false; + } + const uint32_t* p32 = reinterpret_cast(src); + if (!(SDHC_PRSSTAT & SDHC_PRSSTAT_WTA)) { + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + SDHC_PROCTL |= SDHC_PROCTL_CREQ; + } + SDHC_PROCTL |= SDHC_PROCTL_SABGREQ; + if (waitTimeout(isBusyFifoWrite)) { + return sdError(SD_CARD_ERROR_WRITE_FIFO); + } + for (uint32_t iw = 0; iw < 512 / (4 * FIFO_WML); iw++) { + while (0 == (SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN)) { + } + for (uint32_t i = 0; i < FIFO_WML; i++) { + SDHC_DATPORT = p32[i]; + } + p32 += FIFO_WML; + } + m_transferActive = true; + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::writeSector(Sector_t sector, const uint8_t* src) { + if (m_useDma) { + uint8_t* ptr; + uint8_t aligned[512]; + if (3 & reinterpret_cast(src)) { + ptr = aligned; + memcpy(aligned, src, 512); + } else { + ptr = const_cast(src); + } + if (!rdWrSectors(CMD24_DMA_XFERTYP, sector, ptr, 1)) { + return sdError(SD_CARD_ERROR_CMD24); + } + } else { + if (!waitTransferComplete()) { + return false; + } +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + // End transfer with CMD12 if required. + if ((SDHC_BLKATTR & 0XFFFF0000) == 0) { + if (!syncDevice()) { + return false; + } + } +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + if (m_curState != WRITE_STATE || m_curSector != sector) { + if (!syncDevice()) { + return false; + } + if (!writeStart(sector)) { + return false; + } + m_curSector = sector; + m_curState = WRITE_STATE; + } + if (!writeData(src)) { + return false; + } + m_curSector++; + } + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::writeSectors(Sector_t sector, const uint8_t* src, + size_t n) { + if (m_useDma) { + uint8_t* ptr = const_cast(src); + if (3 & reinterpret_cast(ptr)) { + for (size_t i = 0; i < n; i++, sector++, ptr += 512) { + if (!writeSector(sector, ptr)) { + return false; // writeSector will set errorCode. + } + } + return true; + } + if (!rdWrSectors(CMD25_DMA_XFERTYP, sector, ptr, n)) { + return sdError(SD_CARD_ERROR_CMD25); + } + } else { + for (size_t i = 0; i < n; i++) { + if (!writeSector(sector + i, src + i * 512UL)) { + return false; + } + } + } + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::writeStart(Sector_t sector) { + if (yieldTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + +#if defined(__IMXRT1062__) + // Infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKSIZE(512); +#else // defined(__IMXRT1062__) + // Errata - can't do infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(MAX_BLKCNT) | SDHC_BLKATTR_BLKSIZE(512); +#endif // defined(__IMXRT1062__) + if (!cardCommand(CMD25_PGM_XFERTYP, m_highCapacity ? sector : 512 * sector)) { + return sdError(SD_CARD_ERROR_CMD25); + } + return true; +} +//------------------------------------------------------------------------------ +bool TeensySdioCard::writeStop() { return transferStop(); } +#endif // defined(__MK64FX512__) defined(__MK66FX1M0__) defined(__IMXRT1062__) diff --git a/third_party/sdfat/src/SdCard/TeensySdio/TeensySdioCard.h b/third_party/sdfat/src/SdCard/TeensySdio/TeensySdioCard.h new file mode 100644 index 00000000..96ea4fde --- /dev/null +++ b/third_party/sdfat/src/SdCard/TeensySdio/TeensySdioCard.h @@ -0,0 +1,266 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Classes for Teensy SDIO cards. + */ +#pragma once +#include "../../common/SysCall.h" +#include "../SdCardInterface.h" +//------------------------------------------------------------------------------ +class TeensySdioConfig; +/** SdioConfig type for Teensy SDIO */ +typedef TeensySdioConfig SdioConfig; +class TeensySdioCard; +/** Sdio type for Teensy SDIO */ +typedef TeensySdioCard SdioCard; +//------------------------------------------------------------------------------ +/** Use programmed I/O with FIFO. */ +#define FIFO_SDIO 0 +/** Use programmed I/O with DMA. */ +#define DMA_SDIO 1 + +/** + * \class TeensySdioConfig + * \brief SDIO card configuration. + */ +class TeensySdioConfig { + public: + TeensySdioConfig() {} + /** + * TeensySdioConfig constructor. + * \param[in] opt SDIO options. + */ + explicit TeensySdioConfig(uint8_t opt) : m_options(opt) {} + /** \return SDIO card options. */ + uint8_t options() { return m_options; } + /** \return true if DMA_SDIO. */ + bool useDma() { return m_options & DMA_SDIO; } + + private: + uint8_t m_options = FIFO_SDIO; +}; +//------------------------------------------------------------------------------ +/** + * \class TeensySdioCard + * \brief Raw SDIO access to SD and SDHC flash memory cards. + */ +class TeensySdioCard : public SdCardInterface { + public: + /** Initialize the SD card. + * \param[in] config SDIO card configuration. + * \return true for success or false for failure. + */ + bool begin(TeensySdioConfig config); + /** CMD6 Switch mode: Check Function Set Function. + * \param[in] arg CMD6 argument. + * \param[out] status return status data. + * + * \return true for success or false for failure. + */ + bool cardCMD6(uint32_t arg, uint8_t* status) final; + /** Disable an SDIO card. + * not implemented. + */ + void end() final; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + uint32_t __attribute__((error("use sectorCount()"))) cardSize(); +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \note This function requests the SD card to do a flash erase for a + * range of sectors. The data on the card after an erase operation is + * either 0 or 1, depends on the card vendor. The card must support + * single sector erase. + * + * \return true for success or false for failure. + */ + bool erase(Sector_t firstSector, Sector_t lastSector) final; + /** + * \return code for the last error. See SdCardInfo.h for a list of error + * codes. + */ + uint8_t errorCode() const final; + /** \return error data for last error. */ + uint32_t errorData() const final; + /** \return error line for last error. Tmp function for debug. */ + uint32_t errorLine() const; + /** + * Check for busy with CMD13. + * + * \return true if busy else false. + */ + bool isBusy() final; + /** \return the SD clock frequency in kHz. */ + uint32_t kHzSdClk(); + /** + * Read a 512 byte sector from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSector(Sector_t sector, uint8_t* dst) final; + /** + * Read multiple 512 byte sectors from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSectors(Sector_t sector, uint8_t* dst, size_t ns) final; + /** + * Read a card's CID register. The CID contains card identification + * information such as Manufacturer ID, Product name, Product serial + * number and Manufacturing date. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCID(cid_t* cid) final; + /** + * Read a card's CSD register. The CSD contains Card-Specific Data that + * provides information regarding access to the card's contents. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCSD(csd_t* csd) final; + /** Read one data sector in a multiple sector read sequence + * + * \param[out] dst Pointer to the location for the data to be read. + * + * \return true for success or false for failure. + */ + bool readData(uint8_t* dst); + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ + bool readOCR(uint32_t* ocr) final; + /** Read SCR register. + * + * \param[out] scr Value of SCR register. + * \return true for success or false for failure. + */ + bool readSCR(scr_t* scr) final; + /** Return the 64 byte SD Status register. + * \param[out] sds location for 64 status bytes. + * \return true for success or false for failure. + */ + bool readSDS(sds_t* sds) final; + /** Start a read multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. + * + * \return true for success or false for failure. + */ + bool readStart(Sector_t sector); + /** End a read multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool readStop(); + /** \return SDIO card status. */ + uint32_t status() final; + /** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data sectors in the card + * or zero if an error occurs. + */ + Sector_t sectorCount() final; + /** + * Send CMD12 to stop read or write. + * + * \param[in] blocking If true, wait for command complete. + * + * \return true for success or false for failure. + */ + bool stopTransmission(bool blocking); + /** \return success if sync successful. Not for user apps. */ + bool syncDevice() final; + /** Return the card type: SD V1, SD V2 or SDHC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC. + */ + uint8_t type() const final; + /** + * Writes a 512 byte sector to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSector(Sector_t sector, const uint8_t* src) final; + /** + * Write multiple 512 byte sectors to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSectors(Sector_t sector, const uint8_t* src, size_t ns) final; + /** Write one data sector in a multiple sector write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeData(const uint8_t* src); + /** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ + bool writeStart(Sector_t sector); + + /** End a write multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool writeStop(); + + private: + bool readData(void* dst, size_t count); + static const uint8_t IDLE_STATE = 0; + static const uint8_t READ_STATE = 1; + static const uint8_t WRITE_STATE = 2; + Sector_t m_curSector = 0; + uint8_t m_curState = IDLE_STATE; +}; diff --git a/third_party/sdfat/src/SdCard/TeensySdio/TeensySdioDefs.h b/third_party/sdfat/src/SdCard/TeensySdio/TeensySdioDefs.h new file mode 100644 index 00000000..e7665b4d --- /dev/null +++ b/third_party/sdfat/src/SdCard/TeensySdio/TeensySdioDefs.h @@ -0,0 +1,528 @@ +/** + * \file + * \brief Definitions for Teensy HDHC. + */ +#pragma once + +// From Paul's SD.h driver. + +#if defined(__IMXRT1062__) +#define MAKE_REG_MASK(m, s) (((uint32_t)(((uint32_t)(m) << s)))) +#define MAKE_REG_GET(x, m, s) (((uint32_t)(((uint32_t)(x) >> s) & m))) +#define MAKE_REG_SET(x, m, s) (((uint32_t)(((uint32_t)(x) & m) << s))) + +#define SDHC_BLKATTR_BLKSIZE_MASK \ + MAKE_REG_MASK( \ + 0x1FFF, 0) // uint32_t)(((n) & 0x1FFF)<<0) // Transfer Block Size Mask +#define SDHC_BLKATTR_BLKSIZE(n) \ + MAKE_REG_SET(n, 0x1FFF, \ + 0) // uint32_t)(((n) & 0x1FFF)<<0) // Transfer Block Size +#define SDHC_BLKATTR_BLKCNT_MASK \ + MAKE_REG_MASK(0x1FFF, 16) //((uint32_t)0x1FFF<<16) +#define SDHC_BLKATTR_BLKCNT(n) \ + MAKE_REG_SET(n, 0x1FFF, 16) //(uint32_t)(((n) & 0x1FFF)<<16) // Blocks Count + // For Current Transfer + +#define SDHC_XFERTYP_CMDINX(n) \ + MAKE_REG_SET(n, 0x3F, 24) //(uint32_t)(((n) & 0x3F)<<24)// Command Index +#define SDHC_XFERTYP_CMDTYP(n) \ + MAKE_REG_SET(n, 0x3, 22) //(uint32_t)(((n) & 0x3)<<22) // Command Type +#define SDHC_XFERTYP_DPSEL \ + MAKE_REG_MASK(0x1, 21) //((uint32_t)0x00200000) // Data Present Select +#define SDHC_XFERTYP_CICEN \ + MAKE_REG_MASK(0x1, \ + 20) //((uint32_t)0x00100000) // Command Index Check Enable +#define SDHC_XFERTYP_CCCEN \ + MAKE_REG_MASK(0x1, \ + 19) //((uint32_t)0x00080000) // Command CRC Check Enable +#define SDHC_XFERTYP_RSPTYP(n) \ + MAKE_REG_SET(n, 0x3, \ + 16) //(uint32_t)(((n) & 0x3)<<16) // Response Type Select +#define SDHC_XFERTYP_MSBSEL \ + MAKE_REG_MASK(0x1, 5) //((uint32_t)0x00000020) // Multi/Single Block Select +#define SDHC_XFERTYP_DTDSEL \ + MAKE_REG_MASK( \ + 0x1, 4) //((uint32_t)0x00000010) // Data Transfer Direction Select +#define SDHC_XFERTYP_AC12EN \ + MAKE_REG_MASK(0x1, 2) //((uint32_t)0x00000004) // Auto CMD12 Enable +#define SDHC_XFERTYP_BCEN \ + MAKE_REG_MASK(0x1, 1) //((uint32_t)0x00000002) // Block Count Enable +#define SDHC_XFERTYP_DMAEN \ + MAKE_REG_MASK(0x3, 0) //((uint32_t)0x00000001) // DMA Enable + +#define SDHC_PRSSTAT_DLSL_MASK \ + MAKE_REG_MASK(0xFF, 24) //((uint32_t)0xFF000000) // DAT Line Signal Level +#define SDHC_PRSSTAT_CLSL \ + MAKE_REG_MASK(0x1, 23) //((uint32_t)0x00800000) // CMD Line Signal Level +#define SDHC_PRSSTAT_WPSPL MAKE_REG_MASK(0x1, 19) // +#define SDHC_PRSSTAT_CDPL MAKE_REG_MASK(0x1, 18) // +#define SDHC_PRSSTAT_CINS \ + MAKE_REG_MASK(0x1, 16) //((uint32_t)0x00010000) // Card Inserted +#define SDHC_PRSSTAT_TSCD MAKE_REG_MASK(0x1, 15) +#define SDHC_PRSSTAT_RTR MAKE_REG_MASK(0x1, 12) +#define SDHC_PRSSTAT_BREN \ + MAKE_REG_MASK(0x1, 11) //((uint32_t)0x00000800) // Buffer Read Enable +#define SDHC_PRSSTAT_BWEN \ + MAKE_REG_MASK(0x1, 10) //((uint32_t)0x00000400) // Buffer Write Enable +#define SDHC_PRSSTAT_RTA \ + MAKE_REG_MASK(0x1, 9) //((uint32_t)0x00000200) // Read Transfer Active +#define SDHC_PRSSTAT_WTA \ + MAKE_REG_MASK(0x1, 8) //((uint32_t)0x00000100) // Write Transfer Active +#define SDHC_PRSSTAT_SDOFF \ + MAKE_REG_MASK( \ + 0x1, 7) //((uint32_t)0x00000080) // SD Clock Gated Off Internally +#define SDHC_PRSSTAT_PEROFF \ + MAKE_REG_MASK( \ + 0x1, 6) //((uint32_t)0x00000040) // SDHC clock Gated Off Internally +#define SDHC_PRSSTAT_HCKOFF \ + MAKE_REG_MASK( \ + 0x1, 5) //((uint32_t)0x00000020) // System Clock Gated Off Internally +#define SDHC_PRSSTAT_IPGOFF \ + MAKE_REG_MASK( \ + 0x1, 4) //((uint32_t)0x00000010) // Bus Clock Gated Off Internally +#define SDHC_PRSSTAT_SDSTB \ + MAKE_REG_MASK(0x1, 3) //((uint32_t)0x00000008) // SD Clock Stable +#define SDHC_PRSSTAT_DLA \ + MAKE_REG_MASK(0x1, 2) //((uint32_t)0x00000004) // Data Line Active +#define SDHC_PRSSTAT_CDIHB \ + MAKE_REG_MASK(0x1, 1) //((uint32_t)0x00000002) // Command Inhibit (DAT) +#define SDHC_PRSSTAT_CIHB \ + MAKE_REG_MASK(0x1, 0) //((uint32_t)0x00000001) // Command Inhibit (CMD) + +#define SDHC_PROTCT_NONEXACT_BLKRD MAKE_REG_MASK(0x1, 30) // +#define SDHC_PROTCT_BURST_LENEN(n) MAKE_REG_SET(n, 0x7, 12) // +#define SDHC_PROCTL_WECRM \ + MAKE_REG_MASK(0x1, 26) //((uint32_t)0x04000000) // Wakeup Event Enable On + // SD Card Removal +#define SDHC_PROCTL_WECINS \ + MAKE_REG_MASK(0x1, 25) //((uint32_t)0x02000000) // Wakeup Event Enable On + // SD Card Insertion +#define SDHC_PROCTL_WECINT \ + MAKE_REG_MASK(0x1, 24) //((uint32_t)0x01000000) // Wakeup Event Enable On + // Card Interrupt +#define SDHC_PROCTL_RD_DONE_NOBLK MAKE_REG_MASK(0x1, 20) // +#define SDHC_PROCTL_IABG \ + MAKE_REG_MASK(0x1, 19) //((uint32_t)0x00080000) // Interrupt At Block Gap +#define SDHC_PROCTL_RWCTL \ + MAKE_REG_MASK(0x1, 18) //((uint32_t)0x00040000) // Read Wait Control +#define SDHC_PROCTL_CREQ \ + MAKE_REG_MASK(0x1, 17) //((uint32_t)0x00020000) // Continue Request +#define SDHC_PROCTL_SABGREQ \ + MAKE_REG_MASK(0x1, \ + 16) //((uint32_t)0x00010000) // Stop At Block Gap Request +#define SDHC_PROCTL_DMAS(n) \ + MAKE_REG_SET(n, 0x3, 8) //(uint32_t)(((n) & 0x3)<<8) // DMA Select +#define SDHC_PROCTL_CDSS \ + MAKE_REG_MASK(0x1, \ + 7) //((uint32_t)0x00000080) // Card Detect Signal Selection +#define SDHC_PROCTL_CDTL \ + MAKE_REG_MASK(0x1, 6) //((uint32_t)0x00000040) // Card Detect Test Level +#define SDHC_PROCTL_EMODE(n) \ + MAKE_REG_SET(n, 0x3, 4) //(uint32_t)(((n) & 0x3)<<4) // Endian Mode +#define SDHC_PROCTL_EMODE_MASK \ + MAKE_REG_MASK(0x3, 4) //(uint32_t)((0x3)<<4) // Endian Mode +#define SDHC_PROCTL_D3CD \ + MAKE_REG_MASK(0x1, \ + 3) //((uint32_t)0x00000008) // DAT3 As Card Detection Pin +#define SDHC_PROCTL_DTW(n) \ + MAKE_REG_SET(n, 0x3, 1) //(uint32_t)(((n) & 0x3)<<1) // Data Transfer Width, + // 0=1bit, 1=4bit, 2=8bit +#define SDHC_PROCTL_DTW_MASK MAKE_REG_MASK(0x3, 1) //((uint32_t)0x00000006) +#define SDHC_PROCTL_LCTL \ + MAKE_REG_MASK(0x1, 0) //((uint32_t)0x00000001) // LED Control + +#define SDHC_SYSCTL_RSTT MAKE_REG_MASK(0x1, 28) // +#define SDHC_SYSCTL_INITA \ + MAKE_REG_MASK(0x1, 27) //((uint32_t)0x08000000) // Initialization Active +#define SDHC_SYSCTL_RSTD \ + MAKE_REG_MASK( \ + 0x1, 26) //((uint32_t)0x04000000) // Software Reset For DAT Line +#define SDHC_SYSCTL_RSTC \ + MAKE_REG_MASK( \ + 0x1, 25) //((uint32_t)0x02000000) // Software Reset For CMD Line +#define SDHC_SYSCTL_RSTA \ + MAKE_REG_MASK(0x1, 24) //((uint32_t)0x01000000) // Software Reset For ALL +#define SDHC_SYSCTL_DTOCV(n) \ + MAKE_REG_SET( \ + n, 0xF, \ + 16) //(uint32_t)(((n) & 0xF)<<16) // Data Timeout Counter Value +#define SDHC_SYSCTL_DTOCV_MASK MAKE_REG_MASK(0xF, 16) //((uint32_t)0x000F0000) +#define SDHC_SYSCTL_SDCLKFS(n) \ + MAKE_REG_SET(n, 0xFF, \ + 8) //(uint32_t)(((n) & 0xFF)<<8) // SDCLK Frequency Select +#define SDHC_SYSCTL_SDCLKFS_MASK \ + MAKE_REG_MASK(0xFF, 8) //((uint32_t)0x0000FF00) +#define SDHC_SYSCTL_DVS(n) \ + MAKE_REG_SET(n, 0xF, 4) //(uint32_t)(((n) & 0xF)<<4) // Divisor +#define SDHC_SYSCTL_DVS_MASK MAKE_REG_MASK(0xF, 4) //((uint32_t)0x000000F0) + +#define SDHC_SYSCTL_SDCLKEN ((uint32_t)0x00000008) // SD Clock Enable +#define SDHC_SYSCTL_PEREN ((uint32_t)0x00000004) // Peripheral Clock Enable +#define SDHC_SYSCTL_HCKEN ((uint32_t)0x00000002) // System Clock Enable +#define SDHC_SYSCTL_IPGEN ((uint32_t)0x00000001) // IPG Clock Enable + +#define SDHC_IRQSTAT_DMAE \ + MAKE_REG_MASK(0x1, 28) //((uint32_t)0x10000000) // DMA Error +#define SDHC_IRQSTAT_TNE MAKE_REG_MASK(0x1, 26) // +#define SDHC_IRQSTAT_AC12E \ + MAKE_REG_MASK(0x1, 24) //((uint32_t)0x01000000) // Auto CMD12 Error +#define SDHC_IRQSTAT_DEBE \ + MAKE_REG_MASK(0x1, 22) //((uint32_t)0x00400000) // Data End Bit Error +#define SDHC_IRQSTAT_DCE \ + MAKE_REG_MASK(0x1, 21) //((uint32_t)0x00200000) // Data CRC Error +#define SDHC_IRQSTAT_DTOE \ + MAKE_REG_MASK(0x1, 20) //((uint32_t)0x00100000) // Data Timeout Error +#define SDHC_IRQSTAT_CIE \ + MAKE_REG_MASK(0x1, 19) //((uint32_t)0x00080000) // Command Index Error +#define SDHC_IRQSTAT_CEBE \ + MAKE_REG_MASK(0x1, 18) //((uint32_t)0x00040000) // Command End Bit Error +#define SDHC_IRQSTAT_CCE \ + MAKE_REG_MASK(0x1, 17) //((uint32_t)0x00020000) // Command CRC Error +#define SDHC_IRQSTAT_CTOE \ + MAKE_REG_MASK(0x1, 16) //((uint32_t)0x00010000) // Command Timeout Error +#define SDHC_IRQSTAT_TP MAKE_REG_MASK(0x1, 14) // +#define SDHC_IRQSTAT_RTE MAKE_REG_MASK(0x1, 12) // +#define SDHC_IRQSTAT_CINT \ + MAKE_REG_MASK(0x1, 8) //((uint32_t)0x00000100) // Card Interrupt +#define SDHC_IRQSTAT_CRM \ + MAKE_REG_MASK(0x1, 7) //((uint32_t)0x00000080) // Card Removal +#define SDHC_IRQSTAT_CINS \ + MAKE_REG_MASK(0x1, 6) //((uint32_t)0x00000040) // Card Insertion +#define SDHC_IRQSTAT_BRR \ + MAKE_REG_MASK(0x1, 5) //((uint32_t)0x00000020) // Buffer Read Ready +#define SDHC_IRQSTAT_BWR \ + MAKE_REG_MASK(0x1, 4) //((uint32_t)0x00000010) // Buffer Write Ready +#define SDHC_IRQSTAT_DINT \ + MAKE_REG_MASK(0x1, 3) //((uint32_t)0x00000008) // DMA Interrupt +#define SDHC_IRQSTAT_BGE \ + MAKE_REG_MASK(0x1, 2) //((uint32_t)0x00000004) // Block Gap Event +#define SDHC_IRQSTAT_TC \ + MAKE_REG_MASK(0x1, 1) //((uint32_t)0x00000002) // Transfer Complete +#define SDHC_IRQSTAT_CC \ + MAKE_REG_MASK(0x1, 0) //((uint32_t)0x00000001) // Command Complete + +#define SDHC_IRQSTATEN_DMAESEN \ + MAKE_REG_MASK(0x1, 28) //((uint32_t)0x10000000) // DMA Error Status Enable +#define SDHC_IRQSTATEN_TNESEN MAKE_REG_MASK(0x1, 26) // +#define SDHC_IRQSTATEN_AC12ESEN \ + MAKE_REG_MASK( \ + 0x1, 24) //((uint32_t)0x01000000) // Auto CMD12 Error Status Enable +#define SDHC_IRQSTATEN_DEBESEN \ + MAKE_REG_MASK( \ + 0x1, \ + 22) //((uint32_t)0x00400000) // Data End Bit Error Status Enable +#define SDHC_IRQSTATEN_DCESEN \ + MAKE_REG_MASK( \ + 0x1, 21) //((uint32_t)0x00200000) // Data CRC Error Status Enable +#define SDHC_IRQSTATEN_DTOESEN \ + MAKE_REG_MASK( \ + 0x1, \ + 20) //((uint32_t)0x00100000) // Data Timeout Error Status Enable +#define SDHC_IRQSTATEN_CIESEN \ + MAKE_REG_MASK( \ + 0x1, \ + 19) //((uint32_t)0x00080000) // Command Index Error Status Enable +#define SDHC_IRQSTATEN_CEBESEN \ + MAKE_REG_MASK( \ + 0x1, \ + 18) //((uint32_t)0x00040000) // Command End Bit Error Status Enable +#define SDHC_IRQSTATEN_CCESEN \ + MAKE_REG_MASK( \ + 0x1, 17) //((uint32_t)0x00020000) // Command CRC Error Status Enable +#define SDHC_IRQSTATEN_CTOESEN \ + MAKE_REG_MASK( \ + 0x1, \ + 16) //((uint32_t)0x00010000) // Command Timeout Error Status Enable +#define SDHC_IRQSTATEN_TPSEN MAKE_REG_MASK(0x1, 14) // +#define SDHC_IRQSTATEN_RTESEN MAKE_REG_MASK(0x1, 12) // +#define SDHC_IRQSTATEN_CINTSEN \ + MAKE_REG_MASK(0x1, \ + 8) //((uint32_t)0x00000100) // Card Interrupt Status Enable +#define SDHC_IRQSTATEN_CRMSEN \ + MAKE_REG_MASK(0x1, \ + 7) //((uint32_t)0x00000080) // Card Removal Status Enable +#define SDHC_IRQSTATEN_CINSEN \ + MAKE_REG_MASK(0x1, \ + 6) //((uint32_t)0x00000040) // Card Insertion Status Enable +#define SDHC_IRQSTATEN_BRRSEN \ + MAKE_REG_MASK( \ + 0x1, 5) //((uint32_t)0x00000020) // Buffer Read Ready Status Enable +#define SDHC_IRQSTATEN_BWRSEN \ + MAKE_REG_MASK( \ + 0x1, 4) //((uint32_t)0x00000010) // Buffer Write Ready Status Enable +#define SDHC_IRQSTATEN_DINTSEN \ + MAKE_REG_MASK(0x1, \ + 3) //((uint32_t)0x00000008) // DMA Interrupt Status Enable +#define SDHC_IRQSTATEN_BGESEN \ + MAKE_REG_MASK( \ + 0x1, 2) //((uint32_t)0x00000004) // Block Gap Event Status Enable +#define SDHC_IRQSTATEN_TCSEN \ + MAKE_REG_MASK( \ + 0x1, 1) //((uint32_t)0x00000002) // Transfer Complete Status Enable +#define SDHC_IRQSTATEN_CCSEN \ + MAKE_REG_MASK( \ + 0x1, 0) //((uint32_t)0x00000001) // Command Complete Status Enable + +#define SDHC_IRQSIGEN_DMAEIEN \ + MAKE_REG_MASK(0x1, \ + 28) //((uint32_t)0x10000000) // DMA Error Interrupt Enable +#define SDHC_IRQSIGEN_TNEIEN MAKE_REG_MASK(0x1, 26) // +#define SDHC_IRQSIGEN_AC12EIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 24) //((uint32_t)0x01000000) // Auto CMD12 Error Interrupt Enable +#define SDHC_IRQSIGEN_DEBEIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 22) //((uint32_t)0x00400000) // Data End Bit Error Interrupt Enable +#define SDHC_IRQSIGEN_DCEIEN \ + MAKE_REG_MASK( \ + 0x1, 21) //((uint32_t)0x00200000) // Data CRC Error Interrupt Enable +#define SDHC_IRQSIGEN_DTOEIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 20) //((uint32_t)0x00100000) // Data Timeout Error Interrupt Enable +#define SDHC_IRQSIGEN_CIEIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 19) //((uint32_t)0x00080000) // Command Index Error Interrupt Enable +#define SDHC_IRQSIGEN_CEBEIEN \ + MAKE_REG_MASK(0x1, 18) //((uint32_t)0x00040000) // Command End Bit Error + // Interrupt Enable +#define SDHC_IRQSIGEN_CCEIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 17) //((uint32_t)0x00020000) // Command CRC Error Interrupt Enable +#define SDHC_IRQSIGEN_CTOEIEN \ + MAKE_REG_MASK(0x1, 16) //((uint32_t)0x00010000) // Command Timeout Error + // Interrupt Enable +#define SDHC_IRQSIGEN_TPIEN MAKE_REG_MASK(0x1, 14) // +#define SDHC_IRQSIGEN_RTEIEN MAKE_REG_MASK(0x1, 12) // +#define SDHC_IRQSIGEN_CINTIEN \ + MAKE_REG_MASK( \ + 0x1, 8) //((uint32_t)0x00000100) // Card Interrupt Interrupt Enable +#define SDHC_IRQSIGEN_CRMIEN \ + MAKE_REG_MASK( \ + 0x1, 7) //((uint32_t)0x00000080) // Card Removal Interrupt Enable +#define SDHC_IRQSIGEN_CINSIEN \ + MAKE_REG_MASK( \ + 0x1, 6) //((uint32_t)0x00000040) // Card Insertion Interrupt Enable +#define SDHC_IRQSIGEN_BRRIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 5) //((uint32_t)0x00000020) // Buffer Read Ready Interrupt Enable +#define SDHC_IRQSIGEN_BWRIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 4) //((uint32_t)0x00000010) // Buffer Write Ready Interrupt Enable +#define SDHC_IRQSIGEN_DINTIEN \ + MAKE_REG_MASK( \ + 0x1, 3) //((uint32_t)0x00000008) // DMA Interrupt Interrupt Enable +#define SDHC_IRQSIGEN_BGEIEN \ + MAKE_REG_MASK( \ + 0x1, 2) //((uint32_t)0x00000004) // Block Gap Event Interrupt Enable +#define SDHC_IRQSIGEN_TCIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 1) //((uint32_t)0x00000002) // Transfer Complete Interrupt Enable +#define SDHC_IRQSIGEN_CCIEN \ + MAKE_REG_MASK( \ + 0x1, \ + 0) //((uint32_t)0x00000001) // Command Complete Interrupt Enable + +#define SDHC_AC12ERR_SMPLCLK_SEL MAKE_REG_MASK(0x1, 23) // +#define SDHC_AC12ERR_EXEC_TUNING MAKE_REG_MASK(0x1, 22) // +#define SDHC_AC12ERR_CNIBAC12E \ + MAKE_REG_MASK(0x1, 7) //((uint32_t)0x00000080) // Command Not Issued By + // Auto CMD12 Error +#define SDHC_AC12ERR_AC12IE \ + MAKE_REG_MASK(0x1, 4) //((uint32_t)0x00000010) // Auto CMD12 Index Error +#define SDHC_AC12ERR_AC12CE \ + MAKE_REG_MASK(0x1, 3) //((uint32_t)0x00000008) // Auto CMD12 CRC Error +#define SDHC_AC12ERR_AC12EBE \ + MAKE_REG_MASK(0x1, 2) //((uint32_t)0x00000004) // Auto CMD12 End Bit Error +#define SDHC_AC12ERR_AC12TOE \ + MAKE_REG_MASK(0x1, 1) //((uint32_t)0x00000002) // Auto CMD12 Timeout Error +#define SDHC_AC12ERR_AC12NE \ + MAKE_REG_MASK(0x1, 0) //((uint32_t)0x00000001) // Auto CMD12 Not Executed + +#define SDHC_HTCAPBLT_VS18 MAKE_REG_MASK(0x1, 26) // +#define SDHC_HTCAPBLT_VS30 MAKE_REG_MASK(0x1, 25) // +#define SDHC_HTCAPBLT_VS33 MAKE_REG_MASK(0x1, 24) // +#define SDHC_HTCAPBLT_SRS MAKE_REG_MASK(0x1, 23) // +#define SDHC_HTCAPBLT_DMAS MAKE_REG_MASK(0x1, 22) // +#define SDHC_HTCAPBLT_HSS MAKE_REG_MASK(0x1, 21) // +#define SDHC_HTCAPBLT_ADMAS MAKE_REG_MASK(0x1, 20) // +#define SDHC_HTCAPBLT_MBL_VAL MAKE_REG_GET((USDHC1_HOST_CTRL_CAP), 0x7, 16) // +#define SDHC_HTCAPBLT_RETUN_MODE \ + MAKE_REG_GET((USDHC1_HOST_CTRL_CAP), 0x3, 14) // +#define SDHC_HTCAPBLT_TUNE_SDR50 MAKE_REG_MASK(0x1, 13) // +#define SDHC_HTCAPBLT_TIME_RETUN(n) MAKE_REG_SET(n, 0xF, 8) // + +#define SDHC_WML_WR_BRSTLEN_MASK MAKE_REG_MASK(0x1F, 24) // +#define SDHC_WML_RD_BRSTLEN_MASK MAKE_REG_MASK(0x1F, 8) // +#define SDHC_WML_WR_WML_MASK MAKE_REG_MASK(0xFF, 16) // +#define SDHC_WML_RD_WML_MASK MAKE_REG_MASK(0xFF, 0) // +#define SDHC_WML_WR_BRSTLEN(n) \ + MAKE_REG_SET(n, 0x1F, 24) //(uint32_t)(((n) & 0x7F)<<16) // Write Burst Len +#define SDHC_WML_RD_BRSTLEN(n) \ + MAKE_REG_SET(n, 0x1F, 8) //(uint32_t)(((n) & 0x7F)<<0) // Read Burst Len +#define SDHC_WML_WR_WML(n) \ + MAKE_REG_SET(n, 0xFF, \ + 16) //(uint32_t)(((n) & 0x7F)<<16) // Write Watermark Level +#define SDHC_WML_RD_WML(n) \ + MAKE_REG_SET(n, 0xFF, \ + 0) //(uint32_t)(((n) & 0x7F)<<0) // Read Watermark Level +#define SDHC_WML_WRWML(n) \ + MAKE_REG_SET(n, 0xFF, \ + 16) //(uint32_t)(((n) & 0x7F)<<16) // Write Watermark Level +#define SDHC_WML_RDWML(n) \ + MAKE_REG_SET(n, 0xFF, \ + 0) //(uint32_t)(((n) & 0x7F)<<0) // Read Watermark Level + +// Teensy 4.0 only +#define SDHC_MIX_CTRL_DMAEN MAKE_REG_MASK(0x1, 0) // +#define SDHC_MIX_CTRL_BCEN MAKE_REG_MASK(0x1, 1) // +#define SDHC_MIX_CTRL_AC12EN MAKE_REG_MASK(0x1, 2) // +#define SDHC_MIX_CTRL_DDR_EN MAKE_REG_MASK(0x1, 3) // +#define SDHC_MIX_CTRL_DTDSEL MAKE_REG_MASK(0x1, 4) // +#define SDHC_MIX_CTRL_MSBSEL MAKE_REG_MASK(0x1, 5) // +#define SDHC_MIX_CTRL_NIBBLE_POS MAKE_REG_MASK(0x1, 6) // +#define SDHC_MIX_CTRL_AC23EN MAKE_REG_MASK(0x1, 7) // + +#define SDHC_FEVT_CINT \ + MAKE_REG_MASK(0x1, \ + 31) //((uint32_t)0x80000000) // Force Event Card Interrupt +#define SDHC_FEVT_DMAE \ + MAKE_REG_MASK(0x1, 28) //((uint32_t)0x10000000) // Force Event DMA Error +#define SDHC_FEVT_AC12E \ + MAKE_REG_MASK( \ + 0x1, 24) //((uint32_t)0x01000000) // Force Event Auto CMD12 Error +#define SDHC_FEVT_DEBE \ + MAKE_REG_MASK( \ + 0x1, 22) //((uint32_t)0x00400000) // Force Event Data End Bit Error +#define SDHC_FEVT_DCE \ + MAKE_REG_MASK(0x1, \ + 21) //((uint32_t)0x00200000) // Force Event Data CRC Error +#define SDHC_FEVT_DTOE \ + MAKE_REG_MASK( \ + 0x1, 20) //((uint32_t)0x00100000) // Force Event Data Timeout Error +#define SDHC_FEVT_CIE \ + MAKE_REG_MASK( \ + 0x1, 19) //((uint32_t)0x00080000) // Force Event Command Index Error +#define SDHC_FEVT_CEBE \ + MAKE_REG_MASK( \ + 0x1, \ + 18) //((uint32_t)0x00040000) // Force Event Command End Bit Error +#define SDHC_FEVT_CCE \ + MAKE_REG_MASK( \ + 0x1, 17) //((uint32_t)0x00020000) // Force Event Command CRC Error +#define SDHC_FEVT_CTOE \ + MAKE_REG_MASK( \ + 0x1, \ + 16) //((uint32_t)0x00010000) // Force Event Command Timeout Error +#define SDHC_FEVT_CNIBAC12E \ + MAKE_REG_MASK(0x1, 7) //((uint32_t)0x00000080) // Force Event Command Not + // Executed By Auto Command 12 Error +#define SDHC_FEVT_AC12IE \ + MAKE_REG_MASK(0x1, 4) //((uint32_t)0x00000010) // Force Event Auto Command + // 12 Index Error +#define SDHC_FEVT_AC12EBE \ + MAKE_REG_MASK(0x1, 3) //((uint32_t)0x00000008) // Force Event Auto Command + // 12 End Bit Error +#define SDHC_FEVT_AC12CE \ + MAKE_REG_MASK( \ + 0x1, \ + 2) //((uint32_t)0x00000004) // Force Event Auto Command 12 CRC Error +#define SDHC_FEVT_AC12TOE \ + MAKE_REG_MASK(0x1, 1) //((uint32_t)0x00000002) // Force Event Auto Command + // 12 Time Out Error +#define SDHC_FEVT_AC12NE \ + MAKE_REG_MASK(0x1, 0) //((uint32_t)0x00000001) // Force Event Auto Command + // 12 Not Executed + +#define SDHC_ADMAES_ADMADCE MAKE_REG_MASK(0x1, 3) //((uint32_t)0x00000008) +#define SDHC_ADMAES_ADMALME MAKE_REG_MASK(0x1, 2) //((uint32_t)0x00000004) +#define SDHC_ADMAES_ADMAES_MASK MAKE_REG_MASK(0x3, 0) //((uint32_t)0x00000003) + +#define SDHC_MMCBOOT_BOOTBLKCNT(n) \ + MAKE_REG_MASK(0xFF, 16) //(uint32_t)(((n) & 0xFFF)<<16) // stop at block gap + // value of automatic mode +#define SDHC_MMCBOOT_AUTOSABGEN \ + MAKE_REG_MASK(0x1, 7) //((uint32_t)0x00000080) // enable auto stop at + // block gap function +#define SDHC_MMCBOOT_BOOTEN \ + MAKE_REG_MASK(0x1, 6) //((uint32_t)0x00000040) // Boot Mode Enable +#define SDHC_MMCBOOT_BOOTMODE \ + MAKE_REG_MASK(0x1, 5) //((uint32_t)0x00000020) // Boot Mode Select +#define SDHC_MMCBOOT_BOOTACK \ + MAKE_REG_MASK(0x1, 4) //((uint32_t)0x00000010) // Boot Ack Mode Select +#define SDHC_MMCBOOT_DTOCVACK(n) \ + MAKE_REG_MASK( \ + 0xF, \ + 0) //(uint32_t)(((n) & 0xF)<<0) // Boot ACK Time Out Counter Value +// #define SDHC_HOSTVER (*(volatile uint32_t*)0x400B10FC) // Host Controller +// Version + +#define CCM_ANALOG_PFD_528_PFD0_FRAC_MASK 0x3f +#define CCM_ANALOG_PFD_528_PFD0_FRAC(n) \ + ((n) & CCM_ANALOG_PFD_528_PFD0_FRAC_MASK) +#define CCM_ANALOG_PFD_528_PFD1_FRAC_MASK (0x3f << 8) +#define CCM_ANALOG_PFD_528_PFD1_FRAC(n) \ + (((n) << 8) & CCM_ANALOG_PFD_528_PFD1_FRAC_MASK) +#define CCM_ANALOG_PFD_528_PFD2_FRAC_MASK (0x3f << 16) +#define CCM_ANALOG_PFD_528_PFD2_FRAC(n) \ + (((n) << 16) & CCM_ANALOG_PFD_528_PFD2_FRAC_MASK) +#define CCM_ANALOG_PFD_528_PFD3_FRAC_MASK ((0x3f<<24) +#define CCM_ANALOG_PFD_528_PFD3_FRAC(n) \ + (((n) << 24) & CCM_ANALOG_PFD_528_PFD3_FRAC_MASK) + +#define SDHC_DSADDR (USDHC1_DS_ADDR) // DMA System Address register +#define SDHC_BLKATTR (USDHC1_BLK_ATT) // Block Attributes register +#define SDHC_CMDARG (USDHC1_CMD_ARG) // Command Argument register +#define SDHC_XFERTYP (USDHC1_CMD_XFR_TYP) // Transfer Type register +#define SDHC_CMDRSP0 (USDHC1_CMD_RSP0) // Command Response 0 +#define SDHC_CMDRSP1 (USDHC1_CMD_RSP1) // Command Response 1 +#define SDHC_CMDRSP2 (USDHC1_CMD_RSP2) // Command Response 2 +#define SDHC_CMDRSP3 (USDHC1_CMD_RSP3) // Command Response 3 +#define SDHC_DATPORT (USDHC1_DATA_BUFF_ACC_PORT) // Buffer Data Port register +#define SDHC_PRSSTAT (USDHC1_PRES_STATE) // Present State register +#define SDHC_PROCTL (USDHC1_PROT_CTRL) // Protocol Control register +#define SDHC_SYSCTL (USDHC1_SYS_CTRL) // System Control register +#define SDHC_IRQSTAT (USDHC1_INT_STATUS) // Interrupt Status register +#define SDHC_IRQSTATEN \ + (USDHC1_INT_STATUS_EN) // Interrupt Status Enable register +#define SDHC_IRQSIGEN \ + (USDHC1_INT_SIGNAL_EN) // Interrupt Signal Enable register +#define SDHC_AC12ERR \ + (USDHC1_AUTOCMD12_ERR_STATUS) // Auto CMD12 Error Status Register +#define SDHC_HTCAPBLT (USDHC1_HOST_CTRL_CAP) // Host Controller Capabilities +#define SDHC_WML (USDHC1_WTMK_LVL) // Watermark Level Register +#define SDHC_MIX_CTRL (USDHC1_MIX_CTRL) // Mixer Control +#define SDHC_FEVT (USDHC1_FORCE_EVENT) // Force Event register +#define SDHC_ADMAES (USDHC1_ADMA_ERR_STATUS) // ADMA Error Status register +#define SDHC_ADSADDR (USDHC1_ADMA_SYS_ADDR) // ADMA System Addressregister +#define SDHC_VENDOR (USDHC1_VEND_SPEC) // Vendor Specific register +#define SDHC_MMCBOOT (USDHC1_MMC_BOOT) // MMC Boot register +#define SDHC_VENDOR2 (USDHC2_VEND_SPEC2) // Vendor Specific2 register +// +#define IRQ_SDHC IRQ_SDHC1 + +#define SDHC_MAX_DVS (0xF + 1U) +#define SDHC_MAX_CLKFS (0xFF + 1U) +#define SDHC_PREV_DVS(x) ((x) -= 1U) +#define SDHC_PREV_CLKFS(x, y) ((x) >>= (y)) + +#define CCM_CSCDR1_USDHC1_CLK_PODF_MASK (0x7 << 11) +#define CCM_CSCDR1_USDHC1_CLK_PODF(n) (((n) & 0x7) << 11) + +#define IOMUXC_SW_PAD_CTL_PAD_SRE ((0x1 <) < 0) +#define IOMUXC_SW_PAD_CTL_PAD_PKE ((0x1) << 12) +#define IOMUXC_SW_PAD_CTL_PAD_PUE ((0x1) << 13) +#define IOMUXC_SW_PAD_CTL_PAD_HYS ((0x1) << 16) +#define IOMUXC_SW_PAD_CTL_PAD_SPEED(n) (((n) & 0x3) << 6) +#define IOMUXC_SW_PAD_CTL_PAD_PUS(n) (((n) & 0x3) << 14) +#define IOMUXC_SW_PAD_CTL_PAD_PUS_MASK ((0x3) << 14) +#define IOMUXC_SW_PAD_CTL_PAD_DSE(n) (((n) & 0x7) << 3) +#define IOMUXC_SW_PAD_CTL_PAD_DSE_MASK ((0x7) << 3) +#endif // defined(__IMXRT1062__) \ No newline at end of file diff --git a/third_party/sdfat/src/SdFat.h b/third_party/sdfat/src/SdFat.h new file mode 100644 index 00000000..4315bbb4 --- /dev/null +++ b/third_party/sdfat/src/SdFat.h @@ -0,0 +1,507 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief main SdFs include file. + */ +#include "ExFatLib/ExFatLib.h" +#include "FatLib/FatLib.h" +#include "FsLib/FsLib.h" +#include "SdCard/SdCard.h" +#include "common/SysCall.h" +#if INCLUDE_SDIOS +#include "sdios.h" +#endif // INCLUDE_SDIOS +//------------------------------------------------------------------------------ +/** SdFat version for cpp use. */ +#define SD_FAT_VERSION 20301 +/** SdFat version as string. */ +#define SD_FAT_VERSION_STR "2.3.1" +//============================================================================== +/** + * \class SdBase + * \brief base SD file system template class. + */ +template +class SdBase : public Vol { + public: + //---------------------------------------------------------------------------- + /** Initialize SD card and file system. + * + * \param[in] csPin SD card chip select pin. + * \return true for success or false for failure. + */ + bool begin(SdCsPin_t csPin = SS) { +#ifdef BUILTIN_SDCARD // Fake pin for Teensy SDIO. + if (csPin == BUILTIN_SDCARD) { + return begin(SdioConfig(FIFO_SDIO)); + } +#endif // BUILTIN_SDCARD + return begin(SdSpiConfig(csPin, SHARED_SPI)); + } + //---------------------------------------------------------------------------- + /** Initialize SD card and file system. + * + * \param[in] csPin SD card chip select pin. + * \param[in] maxSck Maximum SCK frequency. + * \return true for success or false for failure. + */ + bool begin(SdCsPin_t csPin, uint32_t maxSck) { + return begin(SdSpiConfig(csPin, SHARED_SPI, maxSck)); + } + //---------------------------------------------------------------------------- + /** Initialize SD card and file system for SPI mode. + * + * \param[in] spiConfig SPI configuration. + * \return true for success or false for failure. + */ + bool begin(SdSpiConfig spiConfig) { + return cardBegin(spiConfig) && volumeBegin(); + } + //--------------------------------------------------------------------------- + /** Initialize SD card and file system for SDIO mode. + * + * \param[in] sdioConfig SDIO configuration. + * \return true for success or false for failure. + */ + bool begin(SdioConfig sdioConfig) { + return cardBegin(sdioConfig) && volumeBegin(); + } + //---------------------------------------------------------------------------- + /** \return Pointer to SD card object. */ + SdCard* card() { return m_card; } + //---------------------------------------------------------------------------- + /** Initialize SD card in SPI mode. + * + * \param[in] spiConfig SPI configuration. + * \return true for success or false for failure. + */ + bool cardBegin(SdSpiConfig spiConfig) { + m_card = m_cardFactory.newCard(spiConfig); + return m_card && !m_card->errorCode(); + } + //---------------------------------------------------------------------------- + /** Initialize SD card in SDIO mode. + * + * \param[in] sdioConfig SDIO configuration. + * \return true for success or false for failure. + */ + bool cardBegin(SdioConfig sdioConfig) { + m_card = m_cardFactory.newCard(sdioConfig); + return m_card && !m_card->errorCode(); + } + //---------------------------------------------------------------------------- + /** End use of card. */ + void end() { + Vol::end(); + if (m_card) { + m_card->end(); + } + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + */ + void errorHalt(print_t* pr) { + if (sdErrorCode()) { + pr->print(F("SdError: 0X")); + pr->print(sdErrorCode(), HEX); + pr->print(F(",0X")); + pr->println(sdErrorData(), HEX); + } else if (!Vol::fatType()) { + pr->println(F("Check SD format.")); + } + while (true) { + } + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorHalt(print_t* pr, const char* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorHalt(pr); + } + //---------------------------------------------------------------------------- + /** %Print msg and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorHalt(print_t* pr, const __FlashStringHelper* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorHalt(pr); + } + //---------------------------------------------------------------------------- + /** Format SD card + * + * \param[in] pr Print destination. + * \return true for success else false. + */ + bool format(print_t* pr = nullptr) { + Fmt fmt; + uint8_t* mem = Vol::end(); + if (!mem) { + return false; + } + bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi(); + if (switchSpi && !setDedicatedSpi(true)) { + return false; + } + bool rtn = fmt.format(card(), mem, pr); + if (switchSpi && !setDedicatedSpi(false)) { + return false; + } + return rtn; + } + //---------------------------------------------------------------------------- + /** \return the free cluster count. */ + uint32_t freeClusterCount() { + bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi(); + if (switchSpi && !setDedicatedSpi(true)) { + return 0; + } + uint32_t rtn = Vol::freeClusterCount(); + if (switchSpi && !setDedicatedSpi(false)) { + return 0; + } + return rtn; + } + //---------------------------------------------------------------------------- + /** \return true if can be in dedicated SPI state */ + bool hasDedicatedSpi() { return m_card ? m_card->hasDedicatedSpi() : false; } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + */ + void initErrorHalt(print_t* pr) { + initErrorPrint(pr); + while (true) { + } + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void initErrorHalt(print_t* pr, const char* msg) { + pr->println(msg); + initErrorHalt(pr); + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void initErrorHalt(print_t* pr, const __FlashStringHelper* msg) { + pr->println(msg); + initErrorHalt(pr); + } + //---------------------------------------------------------------------------- + /** Print error details after begin() fails. + * + * \param[in] pr Print destination. + */ + void initErrorPrint(print_t* pr) { + pr->println(F("begin() failed")); + if (sdErrorCode()) { + pr->println(F("Do not reformat the SD.")); + if (sdErrorCode() == SD_CARD_ERROR_CMD0) { + pr->println(F("No card, wrong chip select pin, or wiring error?")); + } + } + errorPrint(pr); + } + //---------------------------------------------------------------------------- + /** \return true if in dedicated SPI state. */ + bool isDedicatedSpi() { return m_card ? m_card->isDedicatedSpi() : false; } + //---------------------------------------------------------------------------- + /** %Print volume FAT/exFAT type. + * + * \param[in] pr Print destination. + */ + void printFatType(print_t* pr) { + if (Vol::fatType() == FAT_TYPE_EXFAT) { + pr->print(F("exFAT")); + } else { + pr->print(F("FAT")); + pr->print(Vol::fatType()); + } + } + //---------------------------------------------------------------------------- + /** %Print SD errorCode and errorData. + * + * \param[in] pr Print destination. + */ + void errorPrint(print_t* pr) { + if (sdErrorCode()) { + pr->print(F("SdError: 0X")); + pr->print(sdErrorCode(), HEX); + pr->print(F(",0X")); + pr->println(sdErrorData(), HEX); + } else if (!Vol::fatType()) { + pr->println(F("Check SD format.")); + } + } + //---------------------------------------------------------------------------- + /** %Print msg, any SD error code. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorPrint(print_t* pr, char const* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorPrint(pr); + } + + /** %Print msg, any SD error code. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorPrint(print_t* pr, const __FlashStringHelper* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorPrint(pr); + } + //---------------------------------------------------------------------------- + /** %Print error info and return. + * + * \param[in] pr Print destination. + */ + void printSdError(print_t* pr) { + if (sdErrorCode()) { + if (sdErrorCode() == SD_CARD_ERROR_CMD0) { + pr->println(F("No card, wrong chip select pin, or wiring error?")); + } + pr->print(F("SD error: ")); + printSdErrorSymbol(pr, sdErrorCode()); + pr->print(F(" = 0x")); + pr->print(sdErrorCode(), HEX); + pr->print(F(",0x")); + pr->println(sdErrorData(), HEX); + } else if (!Vol::fatType()) { + pr->println(F("Check SD format.")); + } + } + //---------------------------------------------------------------------------- + /** \return SD card error code. */ + uint8_t sdErrorCode() { + if (m_card) { + return m_card->errorCode(); + } + return SD_CARD_ERROR_INVALID_CARD_CONFIG; + } + //---------------------------------------------------------------------------- + /** \return SD card error data. */ + uint8_t sdErrorData() { return m_card ? m_card->errorData() : 0; } + //---------------------------------------------------------------------------- + /** Set SPI sharing state + * \param[in] value desired state. + * \return true for success else false; + */ + bool setDedicatedSpi(bool value) { + if (m_card) { + return m_card->setDedicatedSpi(value); + } + return false; + } + //---------------------------------------------------------------------------- + /** \return pointer to base volume */ + Vol* vol() { return reinterpret_cast(this); } + //---------------------------------------------------------------------------- + /** Initialize file system after call to cardBegin. + * + * \return true for success or false for failure. + */ + bool volumeBegin() { + return Vol::begin(m_card) || Vol::begin(m_card, true, 0); + } +#if ENABLE_ARDUINO_SERIAL + /** Print error details after begin() fails. */ + void initErrorPrint() { initErrorPrint(&Serial); } + //---------------------------------------------------------------------------- + /** %Print msg to Serial and halt. + * + * \param[in] msg Message to print. + */ + void errorHalt(const __FlashStringHelper* msg) { errorHalt(&Serial, msg); } + //---------------------------------------------------------------------------- + /** %Print error info to Serial and halt. */ + void errorHalt() { errorHalt(&Serial); } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] msg Message to print. + */ + void errorHalt(const char* msg) { errorHalt(&Serial, msg); } + //---------------------------------------------------------------------------- + /** %Print error info and halt. */ + void initErrorHalt() { initErrorHalt(&Serial); } + //---------------------------------------------------------------------------- + /** %Print msg, any SD error code. + * + * \param[in] msg Message to print. + */ + void errorPrint(const char* msg) { errorPrint(&Serial, msg); } + /** %Print msg, any SD error code. + * + * \param[in] msg Message to print. + */ + void errorPrint(const __FlashStringHelper* msg) { errorPrint(&Serial, msg); } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] msg Message to print. + */ + void initErrorHalt(const char* msg) { initErrorHalt(&Serial, msg); } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] msg Message to print. + */ + void initErrorHalt(const __FlashStringHelper* msg) { + initErrorHalt(&Serial, msg); + } +#endif // ENABLE_ARDUINO_SERIAL + //---------------------------------------------------------------------------- + private: + SdCard* m_card = nullptr; + SdCardFactory m_cardFactory; +}; +//------------------------------------------------------------------------------ +/** + * \class SdFat32 + * \brief SD file system class for FAT volumes. + */ +class SdFat32 : public SdBase { + public: +}; +//------------------------------------------------------------------------------ +/** + * \class SdExFat + * \brief SD file system class for exFAT volumes. + */ +class SdExFat : public SdBase { + public: +}; +//------------------------------------------------------------------------------ +/** + * \class SdFs + * \brief SD file system class for FAT16, FAT32, and exFAT volumes. + */ +class SdFs : public SdBase { + public: +}; +//------------------------------------------------------------------------------ +#if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN) +/** Select type for SdFat. */ +typedef SdFat32 SdFat; +/** Select type for SdBaseFile. */ +typedef FatFile SdBaseFile; +#elif SDFAT_FILE_TYPE == 2 +typedef SdExFat SdFat; +typedef ExFatFile SdBaseFile; +#elif SDFAT_FILE_TYPE == 3 +typedef SdFs SdFat; +typedef FsBaseFile SdBaseFile; +#else // SDFAT_FILE_TYPE +#error Invalid SDFAT_FILE_TYPE +#endif // SDFAT_FILE_TYPE +// +// Only define File if FS.h is not included. +// Line with test for __has_include must not have operators or parentheses. +#if defined __has_include +#if __has_include() +#define HAS_INCLUDE_FS_H +#endif // __has_include() +#endif // defined __has_include +#ifndef HAS_INCLUDE_FS_H +#if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN) +/** Select type for File. */ +typedef File32 File; +#elif SDFAT_FILE_TYPE == 2 +typedef ExFile File; +#elif SDFAT_FILE_TYPE == 3 +typedef FsFile File; +#endif // SDFAT_FILE_TYPE +#elif !defined(DISABLE_FS_H_WARNING) +#warning File not defined because __has_include(FS.h) +#endif // HAS_INCLUDE_FS_H +/** + * \class SdFile + * \brief File with Print. + */ +class SdFile : public PrintFile { + public: + SdFile() {} + /** Create an open SdFile. + * \param[in] path path for file. + * \param[in] oflag open flags. + */ + SdFile(const char* path, oflag_t oflag) { open(path, oflag); } + /** Set the date/time callback function + * + * \param[in] dateTime The user's call back function. The callback + * function is of the form: + * + * \code + * void dateTime(uint16_t* date, uint16_t* time) { + * uint16_t year; + * uint8_t month, day, hour, minute, second; + * + * // User gets date and time from GPS or real-time clock here + * + * // return date using FS_DATE macro to format fields + * *date = FS_DATE(year, month, day); + * + * // return time using FS_TIME macro to format fields + * *time = FS_TIME(hour, minute, second); + * } + * \endcode + * + * Sets the function that is called when a file is created or when + * a file's directory entry is modified by sync(). All timestamps, + * access, creation, and modify, are set when a file is created. + * sync() maintains the last access date and last modify date/time. + * + */ + static void dateTimeCallback(void (*dateTime)(uint16_t* date, + uint16_t* time)) { + FsDateTime::setCallback(dateTime); + } + /** Cancel the date/time callback function. */ + static void dateTimeCallbackCancel() { FsDateTime::clearCallback(); } +}; diff --git a/third_party/sdfat/src/SdFatConfig.h b/third_party/sdfat/src/SdFatConfig.h new file mode 100644 index 00000000..cd068816 --- /dev/null +++ b/third_party/sdfat/src/SdFatConfig.h @@ -0,0 +1,490 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief configuration definitions + */ +#pragma once +#include +#ifdef __AVR__ +#include +#endif // __AVR__ +// To try UTF-8 encoded filenames. +// #define USE_UTF8_LONG_NAMES 1 +// +// For minimum flash size use these settings: +// #define USE_FAT_FILE_FLAG_CONTIGUOUS 0 +// #define ENABLE_DEDICATED_SPI 0 +// #define USE_LONG_FILE_NAMES 0 +// #define SDFAT_FILE_TYPE 1 +// #define CHECK_FLASH_PROGRAMMING 0 // May cause SD to sleep at high current. +// +// Options can be set in a makefile or an IDE like platformIO +// if they are in a #ifndef/#endif block below. +//------------------------------------------------------------------------------ +/* + * Options for file class constructors, assignment operators and destructors. + * + * By default file copy constructors and copy assignment operators are + * private to prevent multiple copies of a instance for a file. + * + * File move constructors and move assignment operators are public to permit + * return of a file instance for compilers that aren't able to use copy elision. + * + */ +/** File copy constructors and copy assignment operators are deleted */ +#define FILE_COPY_CONSTRUCTOR_DELETED 0 +/** File copy constructors and copy assignment operators are private */ +#define FILE_COPY_CONSTRUCTOR_PRIVATE 1 +/** File copy constructors and copy assignment operators are public */ +#define FILE_COPY_CONSTRUCTOR_PUBLIC 2 + +#ifndef FILE_COPY_CONSTRUCTOR_SELECT +/** Specify kind of file copy constructors and copy assignment operators */ +#define FILE_COPY_CONSTRUCTOR_SELECT FILE_COPY_CONSTRUCTOR_PRIVATE +#endif // FILE_COPY_CONSTRUCTOR_SELECT +/** File move constructors and move assignment operators are deleted. */ +#define FILE_MOVE_CONSTRUCTOR_DELETED 0 +/** File move constructors and move assignment operators are public. */ +#define FILE_MOVE_CONSTRUCTOR_PUBLIC 1 + +#ifndef FILE_MOVE_CONSTRUCTOR_SELECT +/** Specify kind of file move constructors and move assignment operators */ +#define FILE_MOVE_CONSTRUCTOR_SELECT FILE_MOVE_CONSTRUCTOR_PUBLIC +#endif // FILE_MOVE_CONSTRUCTOR_SELECT + +#if FILE_MOVE_CONSTRUCTOR_SELECT != FILE_MOVE_CONSTRUCTOR_PUBLIC && \ + FILE_COPY_CONSTRUCTOR_SELECT != FILE_COPY_CONSTRUCTOR_PUBLIC +#error "No public move or copy assign operators" +#endif // FILE_MOVE_CONSTRUCTOR_SELECT && FILE_MOVE_CONSTRUCTOR_SELECT +/** + * Set DESTRUCTOR_CLOSES_FILE nonzero to close a file in its destructor. */ +#ifndef DESTRUCTOR_CLOSES_FILE +#define DESTRUCTOR_CLOSES_FILE 0 +#endif // DESTRUCTOR_CLOSES_FILE +//------------------------------------------------------------------------------ +/** For Debug - must be one on Arduino */ +#ifndef ENABLE_ARDUINO_FEATURES +#define ENABLE_ARDUINO_FEATURES 1 +#endif // ENABLE_ARDUINO_FEATURES +/** For Debug - must be one on Arduino */ +#ifndef ENABLE_ARDUINO_SERIAL +#define ENABLE_ARDUINO_SERIAL 1 +#endif // ENABLE_ARDUINO_SERIAL +/** For Debug - must be one on Arduino */ +#ifndef ENABLE_ARDUINO_STRING +#define ENABLE_ARDUINO_STRING 1 +#endif // ENABLE_ARDUINO_STRING +//------------------------------------------------------------------------------ +#if ENABLE_ARDUINO_FEATURES +#include +#endif // ENABLE_ARDUINO_FEATURES +// Trail Mate ESP-IDF builds use SdFat as a non-Arduino block-device volume +// over the native SDMMC host. SdFat's FAT LFN collision path calls millis() +// even with Arduino features disabled, so provide the tiny compatibility +// shim here instead of pulling in Arduino runtime headers. +#if defined(ESP_PLATFORM) && !ENABLE_ARDUINO_FEATURES +#include "esp_timer.h" +static inline uint32_t millis() { + return static_cast(esp_timer_get_time() / 1000ULL); +} +#endif // defined(ESP_PLATFORM) && !ENABLE_ARDUINO_FEATURES +//------------------------------------------------------------------------------ +/** + * File types for SdFat, File, SdFile, SdBaseFile, fstream, + * ifstream, and ofstream. + * + * Set SDFAT_FILE_TYPE to: + * + * 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. + */ +#ifndef SDFAT_FILE_TYPE +#if defined(__AVR__) && FLASHEND < 0X8000 +// 32K AVR boards. +#define SDFAT_FILE_TYPE 1 +#else // defined(__AVR__) && FLASHEND < 0X8000 +// All other boards. +#define SDFAT_FILE_TYPE 3 +#endif // defined(__AVR__) && FLASHEND < 0X8000 +#endif // SDFAT_FILE_TYPE +//------------------------------------------------------------------------------ +/** + * Set USE_FAT_FILE_FLAG_CONTIGUOUS nonzero to optimize access to + * contiguous files. A small amount of flash is flash is used. + */ +#ifndef USE_FAT_FILE_FLAG_CONTIGUOUS +#define USE_FAT_FILE_FLAG_CONTIGUOUS 1 +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS +//------------------------------------------------------------------------------ +/** + * Set ENABLE_DEDICATED_SPI non-zero to enable dedicated use of the SPI bus. + * Selecting dedicated SPI in SdSpiConfig() will produce better + * performance by using very large multi-block transfers to and + * from the SD card. + * + * Enabling dedicated SPI will cost extra flash and RAM. + */ +#ifndef ENABLE_DEDICATED_SPI +#if defined(__AVR__) && FLASHEND < 0X8000 +// 32K AVR boards. +#define ENABLE_DEDICATED_SPI 1 +#else // defined(__AVR__) && FLASHEND < 0X8000 +// All other boards. +#define ENABLE_DEDICATED_SPI 1 +#endif // defined(__AVR__) && FLASHEND < 0X8000 +#endif // ENABLE_DEDICATED_SPI +//------------------------------------------------------------------------------ +// Driver options +/** + * If the symbol SPI_DRIVER_SELECT is: + * + * 0 - An optimized custom SPI driver is used if it exists + * else the standard library driver is used. + * + * 1 - The standard library driver is always used. + * + * 2 - An external SPI driver of SoftSpiDriver template class is always used. + * + * 3 - An external SPI driver derived from SdSpiBaseClass is always used. + */ +#ifndef SPI_DRIVER_SELECT +#define SPI_DRIVER_SELECT 0 +#endif // SPI_DRIVER_SELECT +/** + * If USE_SPI_ARRAY_TRANSFER is one and the standard SPI library is + * use, the array transfer function, transfer(buf, count), will be used. + * This option will allocate a 512 byte temporary buffer for send. + * This may be faster for some boards. Do not use this with AVR boards. + * + * Warning: the next options are often fastest but only available for some + * non-Arduino board packages. + * + * If USE_SPI_ARRAY_TRANSFER is two use transfer(nullptr, buf, count) for + * receive and transfer(buf, nullptr, count) for send. + * + * If USE_SPI_ARRAY_TRANSFER is three use transfer(nullptr, buf, count) for + * receive and transfer(buf, rxTmp, count) for send. Try this with Adafruit + * SAMD51. + * + * If USE_SPI_ARRAY_TRANSFER is four use transfer(txTmp, buf, count) for + * receive and transfer(buf, rxTmp, count) for send. Try this with STM32. + */ +#ifndef USE_SPI_ARRAY_TRANSFER +#if defined(ARDUINO_ARCH_RP2040) +#define USE_SPI_ARRAY_TRANSFER 2 +#elif defined(ARDUINO_MINIMA) || defined(ARDUINO_UNOR4_WIFI) +#define USE_SPI_ARRAY_TRANSFER 1 +#else // defined(ARDUINO_ARCH_RP2040) +#define USE_SPI_ARRAY_TRANSFER 0 +#endif // defined(ARDUINO_ARCH_RP2040) +#endif // USE_SPI_ARRAY_TRANSFER +//------------------------------------------------------------------------------ +/** + * SD maximum initialization clock rate. + */ +#ifndef SD_MAX_INIT_RATE_KHZ +#define SD_MAX_INIT_RATE_KHZ 400 +#endif // SD_MAX_INIT_RATE_KHZ +/** + * Set USE_BLOCK_DEVICE_INTERFACE nonzero to use a generic block device. + * This allow use of an external FsBlockDevice driver that is derived from + * the FsBlockDeviceInterface like this: + * + * class UsbMscDriver : public FsBlockDeviceInterface { + * ... code for USB mass storage class driver. + * }; + * + * UsbMscDriver usbMsc; + * FsVolume key; + * ... + * + * // Init USB MSC driver. + * if (!usbMsc.begin()) { + * ... handle driver init failure. + * } + * // Init FAT/exFAT volume. + * if (!key.begin(&usbMsc)) { + * ... handle FAT/exFAT failure. + * } + */ +#ifndef USE_BLOCK_DEVICE_INTERFACE +#define USE_BLOCK_DEVICE_INTERFACE 0 +#endif // USE_BLOCK_DEVICE_INTERFACE +//------------------------------------------------------------------------------ +/** + * SD_CHIP_SELECT_MODE defines how the functions + * void sdCsInit(SdCsPin_t pin) {pinMode(pin, OUTPUT);} + * and + * void sdCsWrite(SdCsPin_t pin, bool level) {digitalWrite(pin, level);} + * are defined. + * + * 0 - Internal definition is a strong symbol and can't be replaced. + * + * 1 - Internal definition is a weak symbol and can be replaced. + * + * 2 - No internal definition and must be defined in the application. + */ +#ifndef SD_CHIP_SELECT_MODE +#define SD_CHIP_SELECT_MODE 0 +#endif // SD_CHIP_SELECT_MODE +/** Type for card chip select pin. */ +typedef uint8_t SdCsPin_t; +//------------------------------------------------------------------------------ +/** + * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN) in FAT16/FAT32. + * exFAT always uses long file names. + * + * Long File Name are limited to a maximum length of 255 characters. + * + * This implementation allows 7-bit characters in the range + * 0X20 to 0X7E except the following characters are not allowed: + * + * < (less than) + * > (greater than) + * : (colon) + * " (double quote) + * / (forward slash) + * \ (backslash) + * | (vertical bar or pipe) + * ? (question mark) + * * (asterisk) + * + */ +#ifndef USE_LONG_FILE_NAMES +#define USE_LONG_FILE_NAMES 1 +#endif // USE_LONG_FILE_NAMES +/** + * Set USE_UTF8_LONG_NAMES nonzero to use UTF-8 file names. Use of UTF-8 names + * will require significantly more flash memory and a small amount of extra + * RAM. + * + * UTF-8 filenames allow encoding of 1,112,064 code points in Unicode using + * one to four one-byte (8-bit) code units. + * + * As of Version 13.0, the Unicode Standard defines 143,859 characters. + * + * getName() will return UTF-8 strings and printName() will write UTF-8 strings. + */ +#ifndef USE_UTF8_LONG_NAMES +#define USE_UTF8_LONG_NAMES 0 +#endif // USE_UTF8_LONG_NAMES + +#if USE_UTF8_LONG_NAMES && !USE_LONG_FILE_NAMES +#error "USE_UTF8_LONG_NAMES requires USE_LONG_FILE_NAMES to be non-zero." +#endif // USE_UTF8_LONG_NAMES && !USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +/** + * Set MAINTAIN_FREE_CLUSTER_COUNT nonzero to keep the count of free clusters + * updated. This will increase the speed of the freeClusterCount() call + * after the first call. Extra flash will be required. + */ +#ifndef MAINTAIN_FREE_CLUSTER_COUNT +#define MAINTAIN_FREE_CLUSTER_COUNT 0 +#endif // MAINTAIN_FREE_CLUSTER_COUNT +//------------------------------------------------------------------------------ +/** + * Set the default file time stamp when a RTC callback is not used. + * A valid date and time is required by the FAT/exFAT standard. + * + * The default below is YYYY-01-01 00:00:00 midnight where YYYY is + * the compile year from the __DATE__ macro. This is easy to recognize + * as a placeholder for a correct date/time. + * + * The full compile date is: + * FS_DATE(compileYear(), compileMonth(), compileDay()) + * + * The full compile time is: + * FS_TIME(compileHour(), compileMinute(), compileSecond()) + */ +#define FS_DEFAULT_DATE FS_DATE(compileYear(), 1, 1) +/** 00:00:00 midnight */ +#define FS_DEFAULT_TIME FS_TIME(0, 0, 0) +//------------------------------------------------------------------------------ +/** + * If CHECK_FLASH_PROGRAMMING is zero, overlap of single sector flash + * programming and other operations will be allowed for faster write + * performance. + * + * Some cards will not sleep in low power mode unless CHECK_FLASH_PROGRAMMING + * is non-zero. + */ +#ifndef CHECK_FLASH_PROGRAMMING +#define CHECK_FLASH_PROGRAMMING 1 +#endif // CHECK_FLASH_PROGRAMMING +//------------------------------------------------------------------------------ +/** + * To enable SD card CRC checking for SPI, set USE_SD_CRC nonzero. + * + * Set USE_SD_CRC to 1 to use a smaller CRC-CCITT function. This function + * is slower for AVR but may be fast for ARM and other processors. + * + * Set USE_SD_CRC to 2 to used a larger table driven CRC-CCITT function. This + * function is faster for AVR but may be slower for ARM and other processors. + */ +#ifndef USE_SD_CRC +#define USE_SD_CRC 0 +#endif // USE_SD_CRC +//------------------------------------------------------------------------------ +/** If the symbol USE_FCNTL_H is nonzero, open flags for access modes O_RDONLY, + * O_WRONLY, O_RDWR and the open modifiers O_APPEND, O_CREAT, O_EXCL, O_SYNC + * will be defined by including the system file fcntl.h. + */ +#ifndef USE_FCNTL_H +#if defined(__AVR__) +// AVR fcntl.h does not define open flags. +#define USE_FCNTL_H 0 +#elif defined(__arm__) +// ARM gcc defines open flags. +#define USE_FCNTL_H 1 +#elif defined(ESP32) +#define USE_FCNTL_H 1 +#else // defined(__AVR__) +#define USE_FCNTL_H 0 +#endif // defined(__AVR__) +#endif // USE_FCNTL_H +//------------------------------------------------------------------------------ +/** + * Set INCLUDE_SDIOS nonzero to include sdios.h in SdFat.h. + * sdios.h provides C++ style IO Streams. + */ +#ifndef INCLUDE_SDIOS +#define INCLUDE_SDIOS 0 +#endif // INCLUDE_SDIOS +//------------------------------------------------------------------------------ +/** + * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes. + * FAT12 has not been well tested and requires additional flash. + */ +#ifndef FAT12_SUPPORT +#define FAT12_SUPPORT 0 +#endif // FAT12_SUPPORT +//------------------------------------------------------------------------------ +/** + * Call flush for endl if ENDL_CALLS_FLUSH is nonzero + * + * The standard for iostreams is to call flush. This is very costly for + * SdFat. Each call to flush causes 2048 bytes of I/O to the SD. + * + * SdFat has a single 512 byte buffer for SD I/O so it must write the current + * data sector to the SD, read the directory sector from the SD, update the + * directory entry, write the directory sector to the SD and read the data + * sector back into the buffer. + * + * The SD flash memory controller is not designed for this many rewrites + * so performance may be reduced by more than a factor of 100. + * + * If ENDL_CALLS_FLUSH is zero, you must call flush and/or close to force + * all data to be written to the SD. + */ +#ifndef ENDL_CALLS_FLUSH +#define ENDL_CALLS_FLUSH 0 +#endif // ENDL_CALLS_FLUSH +//------------------------------------------------------------------------------ +/** + * Set USE_SIMPLE_LITTLE_ENDIAN nonzero for little endian processors + * with no memory alignment restrictions. + */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && \ + (defined(__AVR__) || defined(__ARM_FEATURE_UNALIGNED)) +#define USE_SIMPLE_LITTLE_ENDIAN 1 +#else // __BYTE_ORDER_ +#define USE_SIMPLE_LITTLE_ENDIAN 0 +#endif // __BYTE_ORDER_ +//------------------------------------------------------------------------------ +/** + * Set USE_SEPARATE_FAT_CACHE nonzero to use a second 512 byte cache + * for FAT16/FAT32 table entries. This improves performance for large + * writes that are not a multiple of 512 bytes. + */ +#ifdef __arm__ +#define USE_SEPARATE_FAT_CACHE 1 +#else // __arm__ +#define USE_SEPARATE_FAT_CACHE 0 +#endif // __arm__ +//------------------------------------------------------------------------------ +/** + * Set USE_EXFAT_BITMAP_CACHE nonzero to use a second 512 byte cache + * for exFAT bitmap entries. This improves performance for large + * writes that are not a multiple of 512 bytes. + */ +#ifdef __arm__ +#define USE_EXFAT_BITMAP_CACHE 1 +#else // __arm__ +#define USE_EXFAT_BITMAP_CACHE 0 +#endif // __arm__ +//------------------------------------------------------------------------------ +/** + * Set USE_MULTI_SECTOR_IO nonzero to use multi-sector SD read/write. + * + * Don't use mult-sector read/write on small AVR boards. + */ +#if defined(RAMEND) && RAMEND < 3000 +#define USE_MULTI_SECTOR_IO 0 +#else // RAMEND +#define USE_MULTI_SECTOR_IO 1 +#endif // RAMEND +//------------------------------------------------------------------------------ +/** Enable SDIO driver if available. */ +#if defined(ARDUINO_ARCH_RP2040) +#define HAS_PIO_SDIO 1 +#define HAS_SDIO_CLASS 1 +#endif // defined(ARDUINO_ARCH_RP2040) + +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +// Pseudo pin select for SDIO. +#ifndef BUILTIN_SDCARD +#define BUILTIN_SDCARD 254 +#endif // BUILTIN_SDCARD +// SPI for built-in card. +#ifndef SDCARD_SPI +#define SDCARD_SPI SPI1 +#define SDCARD_MISO_PIN 59 +#define SDCARD_MOSI_PIN 61 +#define SDCARD_SCK_PIN 60 +#define SDCARD_SS_PIN 62 +#endif // SDCARD_SPI +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +#if defined(__IMXRT1062__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) +#define HAS_SDIO_CLASS 1 +#define HAS_TEENSY_SDIO 1 +#endif // defined(__IMXRT1062__) +//------------------------------------------------------------------------------ +/** + * Determine the default SPI configuration. + */ +#if (defined(__AVR__) && defined(SPDR) && defined(SPSR) && defined(SPIF)) || \ + (defined(__AVR__) && defined(SPI0) && defined(SPI_RXCIF_bm)) || \ + defined(ARDUINO_SAM_DUE) || defined(STM32_CORE_VERSION) || \ + (defined(CORE_TEENSY) && defined(__arm__)) +#define SD_HAS_CUSTOM_SPI 1 +#else // SD_HAS_CUSTOM_SPI +// Use standard SPI library. +#define SD_HAS_CUSTOM_SPI 0 +#endif // SD_HAS_CUSTOM_SPI +//------------------------------------------------------------------------------ +#ifndef HAS_SDIO_CLASS +/** Default is no SDIO. */ +#define HAS_SDIO_CLASS 0 +#endif // HAS_SDIO_CLASS diff --git a/third_party/sdfat/src/common/ArduinoFiles.h b/third_party/sdfat/src/common/ArduinoFiles.h new file mode 100644 index 00000000..0562b9c7 --- /dev/null +++ b/third_party/sdfat/src/common/ArduinoFiles.h @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "SysCall.h" +//------------------------------------------------------------------------------ +/** Arduino SD.h style flag for open for read. */ +#ifndef FILE_READ +#define FILE_READ O_RDONLY +#endif // FILE_READ +/** Arduino SD.h style flag for open at EOF for read/write with create. */ +#ifndef FILE_WRITE +#define FILE_WRITE (O_RDWR | O_CREAT | O_AT_END) +#endif // FILE_WRITE +//------------------------------------------------------------------------------ +/** + * \class PrintFile + * \brief PrintFile class. + */ +template +class PrintFile : public print_t, public BaseFile { + public: + using BaseFile::clearWriteError; + using BaseFile::getWriteError; + using BaseFile::write; + + /** Ensure that any bytes written to the file are saved to the SD card. */ + void flush() { BaseFile::sync(); } // No override - not always in Print.h + /** Write a single byte. + * \param[in] b byte to write. + * \return one for success. + */ + size_t write(uint8_t b) override { return BaseFile::write(&b, 1); } + + /** Write data to an open file. + * \param[in] buffer pointer + * \param[in] size of the buffer + * \return number of bytes actually written + */ + size_t write(const uint8_t* buffer, size_t size) override { + return BaseFile::write(buffer, size); + } +}; +//------------------------------------------------------------------------------ +/** + * \class StreamFile + * \brief StreamFile class. + */ +template +class StreamFile : public stream_t, public BaseFile { + public: + using BaseFile::clearWriteError; + using BaseFile::getWriteError; + using BaseFile::read; + using BaseFile::write; + StreamFile() {} + /** \return number of bytes available from the current position to EOF + * or INT_MAX if more than INT_MAX bytes are available. + */ + int available() override { return BaseFile::available(); } + /** Ensure that any bytes written to the file are saved to the SD card. */ + void flush() override { BaseFile::sync(); } + + /** This function reports if the current file is a directory or not. + * \return true if the file is a directory. + */ + bool isDirectory() { return BaseFile::isDir(); } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + char* __attribute__((error("use getName(name, size)"))) name(); +#endif // DOXYGEN_SHOULD_SKIP_THIS + + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek() override { return BaseFile::peek(); } + /** \return the current file position. */ + PosType position() { return BaseFile::curPosition(); } + + /** Read the next byte from a file. + * + * \return For success return the next byte in the file as an int. + * If an error occurs or end of file is reached return -1. + */ + int read() override { return BaseFile::read(); } + + /** Rewind a file if it is a directory */ + void rewindDirectory() { + if (BaseFile::isDir()) { + BaseFile::rewind(); + } + } + /** + * Seek to a new position in the file, which must be between + * 0 and the size of the file (inclusive). + * + * \param[in] pos the new file position. + * \return true for success or false for failure. + */ + bool seek(PosType pos) { return BaseFile::seekSet(pos); } + /** \return the file's size. */ + PosType size() { return BaseFile::fileSize(); } + /** Write a byte to a file. Required by the Arduino Print class. + * \param[in] b the byte to be written. + * Use getWriteError to check for errors. + * \return 1 for success and 0 for failure. + */ + size_t write(uint8_t b) override { return BaseFile::write(b); } + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buffer Pointer to the location of the data to be written. + * + * \param[in] size Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a size. + */ + size_t write(const uint8_t* buffer, size_t size) override { + return BaseFile::write(buffer, size); + } +}; diff --git a/third_party/sdfat/src/common/CPPLINT.cfg b/third_party/sdfat/src/common/CPPLINT.cfg new file mode 100644 index 00000000..d575b373 --- /dev/null +++ b/third_party/sdfat/src/common/CPPLINT.cfg @@ -0,0 +1,3 @@ +exclude_files=PrintBasic.cpp +exclude_files=PrintBasic.h +exclude_files=PrintTemplates.h diff --git a/third_party/sdfat/src/common/CompileDateTime.h b/third_party/sdfat/src/common/CompileDateTime.h new file mode 100644 index 00000000..fb900725 --- /dev/null +++ b/third_party/sdfat/src/common/CompileDateTime.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include +// Note - these functions will compile to a few bytes +// since they are evaluated at compile time. + +/** \return year field of the __DATE__ macro. */ +constexpr uint16_t compileYear() { + return 1000 * (__DATE__[7] - '0') + 100 * (__DATE__[8] - '0') + + 10 * (__DATE__[9] - '0') + (__DATE__[10] - '0'); +} +/** \return month field of the __DATE__ macro. */ +constexpr uint8_t compileMonth() { + return __DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n' ? 1 + : __DATE__[0] == 'F' && __DATE__[1] == 'e' && __DATE__[2] == 'b' ? 2 + : __DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r' ? 3 + : __DATE__[0] == 'A' && __DATE__[1] == 'p' && __DATE__[2] == 'r' ? 4 + : __DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y' ? 5 + : __DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n' ? 6 + : __DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l' ? 7 + : __DATE__[0] == 'A' && __DATE__[1] == 'u' && __DATE__[2] == 'g' ? 8 + : __DATE__[0] == 'S' && __DATE__[1] == 'e' && __DATE__[2] == 'p' ? 9 + : __DATE__[0] == 'O' && __DATE__[1] == 'c' && __DATE__[2] == 't' ? 10 + : __DATE__[0] == 'N' && __DATE__[1] == 'o' && __DATE__[2] == 'v' ? 11 + : __DATE__[0] == 'D' && __DATE__[1] == 'e' && __DATE__[2] == 'c' ? 12 + : 0; +} +/** \return day field of the __DATE__ macro. */ +constexpr uint8_t compileDay() { + return 10 * ((__DATE__[4] == ' ' ? '0' : __DATE__[4]) - '0') + + (__DATE__[5] - '0'); +} +/** \return hour field of the __TIME__ macro. */ +constexpr uint8_t compileHour() { + return 10 * (__TIME__[0] - '0') + __TIME__[1] - '0'; +} +/** \return minute field of the __TIME__ macro. */ +constexpr uint8_t compileMinute() { + return 10 * (__TIME__[3] - '0') + __TIME__[4] - '0'; +} +/** \return second field of the __TIME__ macro. */ +constexpr uint8_t compileSecond() { + return 10 * (__TIME__[6] - '0') + __TIME__[7] - '0'; +} diff --git a/third_party/sdfat/src/common/DateLib.h b/third_party/sdfat/src/common/DateLib.h new file mode 100644 index 00000000..05cb05a2 --- /dev/null +++ b/third_party/sdfat/src/common/DateLib.h @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2011-2022 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once + +/** + * @file + */ +/** Various calendar algorithms. */ +#include +/** Set USE_1901_2099 nonzero for year in [1901,2099]. */ +#define USE_1901_2099 1 + +/** EPOCH starts on Jan 1 of EPOCH_YEAR. */ +#define EPOCH_YEAR 1970 + +/** Determine leap year. + * @param[in] Y year + * @return true if Y is a leap year + */ +inline bool leap(uint16_t Y) { +#if USE_1901_2099 + return (Y & 3) == 0; +#else // USE_1901_2099 + return Y % 4 != 0 ? false : Y % 100 != 0 ? true : Y % 400 == 0; +#endif // USE_1901_2099 +} + +/** Number of days in the year before the current month + * @param[in] Y year + * @param[in] M month 0 < M < 13 + * @return days in year before current month [0, 335] + * Probably from "Astronomical Algorithms" ISBN 0-943396-61-1 + */ +inline uint16_t daysBeforeMonth(uint16_t Y, uint8_t M) { + bool L = leap(Y); + return (275 * M / 9) - (M > 2 ? L ? 1 : 2 : 0) - 30; +} + +/** Number of days in a month. + * @param[in] Y year in range supported by leap(). + * @param[in] M month 0 < M < 13 + * @return Count of days in month [1, 31]. + */ +inline uint8_t daysInMonth(uint16_t Y, uint8_t M) { + bool L = leap(Y); + return M == 2 ? (L ? 29 : 28) : M < 8 ? 30 + (M & 1) : 31 - (M & 1); +} + +/** Day of week with Sunday == 0. + * @param[in] Y year + * @param[in] M month 1 <= M <= 12 + * @param[in] D day 1 <= D <= (last day of month) + * @return Day of week [0,6]. + */ +inline uint8_t dayOfWeek(uint16_t Y, uint8_t M, uint8_t D) { + if (M < 3) { + M += 12; + Y--; + } +#if USE_1901_2099 + // For the year in [1901,2099], - Y /100 + Y / 400 is -15. + return (2 + D + (13 * M - 2) / 5 + Y + Y / 4 - 15) % 7; +#else // USE_1901_2099 + return (2 + D + (13 * M - 2) / 5 + Y + Y / 4 - Y / 100 + Y / 400) % 7; +#endif // USE_1901_2099 +} + +/** Day of year with Jan1 == 0. + * + * @param[in] Y year in range supported by leap(). + * @param[in] M month 0 < M < 13 + * @param[in] D day 0 < D <= length of month + * @return Day of year [0, 365] + **/ +inline uint16_t dayOfYear(uint16_t Y, uint8_t M, uint8_t D) { + return daysBeforeMonth(Y, M) + D - 1; +} + +/** Day of year to day + * @param[in] doy day of year 0 <= yday <= 365 + * @param[in] Y year + * @param[in] M month 1 <= M <= 12 + * @return day of month + * Based on http://ss64.net/merlyn/daycount.htm#DYZ + */ +inline uint8_t dayOfYearToDay(uint16_t doy, uint16_t Y, uint8_t M) { + bool L = leap(Y); + return doy + 1 - (M < 3 ? 31 * (M - 1) : (153 * M - 2) / 5 - (L ? 31 : 32)); +} + +/** Day of year to month + * @param[in] doy day of year 0 <= yday <= 365 + * @param[in] Y year + * @return month [1,12] + * Based on http://ss64.net/merlyn/daycount.htm#DYZ + */ +inline uint8_t dayOfYearToMonth(uint16_t doy, uint16_t Y) { + bool L = leap(Y); + return doy < 31 ? 1 : 1 + (303 + 5 * (doy - (L ? 59 : 58))) / 153; +} + +/** Count of days since Epoch. + * 1900 < EPOCH_YEAR, MAX_YEAR < 2100, (MAX_YEAR - EPOCH_YEAR) < 178. + * @param[in] Y year (EPOCH_YEAR <= Y <= MAX_YEAR) + * @param[in] M month 1 <= M <= 12 + * @param[in] D day 1 <= D <= 31 + * @return Count of days since epoch + * + * Derived from Zeller's congruence + */ +inline uint16_t epochDay(uint16_t Y, uint8_t M, uint8_t D) { + if (M < 3) { + M += 12; + Y--; + } + return 365 * (Y + 1 - EPOCH_YEAR) + Y / 4 - (EPOCH_YEAR - 1) / 4 + + (153 * M - 2) / 5 + D - 398; +} + +/** epoch day to day of week (Sunday == 0) + * 1900 < EPOCH_YEAR, MAX_YEAR < 2100, (MAX_YEAR - EPOCH_YEAR) < 178. + * @param[in] eday count of days since epoch. + * @return day of week (Sunday == 0) + **/ +inline uint8_t epochDayToDayOfWeek(uint16_t eday) { + return (eday + EPOCH_YEAR - 1 + (EPOCH_YEAR - 1) / 4) % 7; +} + +/** Day of epoch to year + * 1900 < EPOCH_YEAR, MAX_YEAR < 2100, (MAX_YEAR - EPOCH_YEAR) < 178. + * @param[in] eday count of days since epoch + * @return year for count of days since epoch + */ +inline uint16_t epochDayToYear(uint16_t eday) { + return EPOCH_YEAR + + (eday - (eday + 365 * (1 + (EPOCH_YEAR - 1) % 4)) / 1461) / 365; +} + +/** epoch day to year, month, day + * 1900 < EPOCH_YEAR, MAX_YEAR < 2100, (MAX_YEAR - EPOCH_YEAR) < 178. + * Based on "Software, Practice and Experience", Vol. 23 (1993) page 384. + * + * @param[in] eday count of days since epoch + * @param[out] Y year + * @param[out] M month + * @param[out] D day + */ +inline void epochDayToYMD(uint16_t eday, uint16_t* Y, uint8_t* M, uint8_t* D) { + // Align day number with leap year. + eday += 365 * (3 - EPOCH_YEAR % 4); + uint8_t n4 = eday / 1461; + eday = eday % 1461; + uint8_t n1 = eday / 365; + eday = eday % 365; + uint16_t yr = 4 * n4 + n1 + EPOCH_YEAR - (3 - EPOCH_YEAR % 4); + if (n1 == 4) { + // last day of a leap year. + *Y = yr - 1; + *M = 12; + *D = 31; + } else { + *Y = yr; + *M = dayOfYearToMonth(eday, yr); + *D = eday - daysBeforeMonth(yr, *M) + 1; + } +} diff --git a/third_party/sdfat/src/common/DebugMacros.h b/third_party/sdfat/src/common/DebugMacros.h new file mode 100644 index 00000000..a49a9015 --- /dev/null +++ b/third_party/sdfat/src/common/DebugMacros.h @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "SysCall.h" + +// 0 - disable, 1 - fail, halt 2 - fail, halt, warn +#define USE_DBG_MACROS 0 + +#if USE_DBG_MACROS +#include "Arduino.h" +#ifndef DBG_FILE +#error DBG_FILE not defined +#endif // DBG_FILE + +__attribute__((unused)) static void dbgFail(uint16_t line) { + Serial.print(F("DBG_FAIL: ")); + Serial.print(F(DBG_FILE)); + Serial.write('.'); + Serial.println(line); +} +__attribute__((unused)) static void dbgHalt(uint16_t line) { + Serial.print(F("DBG_HALT: ")); + Serial.print(F(DBG_FILE)); + Serial.write('.'); + Serial.println(line); + while (true) { + } +} +#define DBG_FAIL_MACRO dbgFail(__LINE__) +#define DBG_HALT_MACRO dbgHalt(__LINE__) +#define DBG_HALT_IF(b) \ + if (b) { \ + dbgHalt(__LINE__); \ + } + +#else // USE_DBG_MACROS +#define DBG_FAIL_MACRO +#define DBG_HALT_MACRO +#define DBG_HALT_IF(b) +#endif // USE_DBG_MACROS + +#if USE_DBG_MACROS > 1 +__attribute__((unused)) static void dbgWarn(uint16_t line) { + Serial.print(F("DBG_WARN: ")); + Serial.print(F(DBG_FILE)); + Serial.write('.'); + Serial.println(line); +} +#define DBG_WARN_MACRO dbgWarn(__LINE__) +#define DBG_WARN_IF(b) \ + if (b) { \ + dbgWarn(__LINE__); \ + } +#else // USE_DBG_MACROS > 1 +#define DBG_WARN_MACRO +#define DBG_WARN_IF(b) +#endif // USE_DBG_MACROS > 1 diff --git a/third_party/sdfat/src/common/FmtNumber.cpp b/third_party/sdfat/src/common/FmtNumber.cpp new file mode 100644 index 00000000..f40935dc --- /dev/null +++ b/third_party/sdfat/src/common/FmtNumber.cpp @@ -0,0 +1,521 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FmtNumber.h" +// always use fmtBase10() - seems fast even on teensy 3.6. +#define USE_FMT_BASE10 1 + +// Use Stimmer div/mod 10 on avr +#ifdef __AVR__ +#include +#define USE_STIMMER +#endif // __AVR__ +//------------------------------------------------------------------------------ +// Stimmer div/mod 10 for AVR +// this code fragment works out i/10 and i%10 by calculating +// i*(51/256)*(256/255)/2 == i*51/510 == i/10 +// by "j.k" I mean 32.8 fixed point, j is integer part, k is fractional part +// j.k = ((j+1.0)*51.0)/256.0 +// (we add 1 because we will be using the floor of the result later) +// divmod10_asm16 and divmod10_asm32 are public domain code by Stimmer. +// http://forum.arduino.cc/index.php?topic=167414.msg1293679#msg1293679 +#define divmod10_asm16(in32, mod8, tmp8) \ + asm volatile( \ + " ldi %2,51 \n\t" \ + " mul %A0,%2 \n\t" \ + " clr %A0 \n\t" \ + " add r0,%2 \n\t" \ + " adc %A0,r1 \n\t" \ + " mov %1,r0 \n\t" \ + " mul %B0,%2 \n\t" \ + " clr %B0 \n\t" \ + " add %A0,r0 \n\t" \ + " adc %B0,r1 \n\t" \ + " clr r1 \n\t" \ + " add %1,%A0 \n\t" \ + " adc %A0,%B0 \n\t" \ + " adc %B0,r1 \n\t" \ + " add %1,%B0 \n\t" \ + " adc %A0,r1 \n\t" \ + " adc %B0,r1 \n\t" \ + " lsr %B0 \n\t" \ + " ror %A0 \n\t" \ + " ror %1 \n\t" \ + " ldi %2,10 \n\t" \ + " mul %1,%2 \n\t" \ + " mov %1,r1 \n\t" \ + " clr r1 \n\t" \ + : "+r"(in32), "=d"(mod8), "=d"(tmp8) \ + : \ + : "r0") + +#define divmod10_asm32(in32, mod8, tmp8) \ + asm volatile( \ + " ldi %2,51 \n\t" \ + " mul %A0,%2 \n\t" \ + " clr %A0 \n\t" \ + " add r0,%2 \n\t" \ + " adc %A0,r1 \n\t" \ + " mov %1,r0 \n\t" \ + " mul %B0,%2 \n\t" \ + " clr %B0 \n\t" \ + " add %A0,r0 \n\t" \ + " adc %B0,r1 \n\t" \ + " mul %C0,%2 \n\t" \ + " clr %C0 \n\t" \ + " add %B0,r0 \n\t" \ + " adc %C0,r1 \n\t" \ + " mul %D0,%2 \n\t" \ + " clr %D0 \n\t" \ + " add %C0,r0 \n\t" \ + " adc %D0,r1 \n\t" \ + " clr r1 \n\t" \ + " add %1,%A0 \n\t" \ + " adc %A0,%B0 \n\t" \ + " adc %B0,%C0 \n\t" \ + " adc %C0,%D0 \n\t" \ + " adc %D0,r1 \n\t" \ + " add %1,%B0 \n\t" \ + " adc %A0,%C0 \n\t" \ + " adc %B0,%D0 \n\t" \ + " adc %C0,r1 \n\t" \ + " adc %D0,r1 \n\t" \ + " add %1,%D0 \n\t" \ + " adc %A0,r1 \n\t" \ + " adc %B0,r1 \n\t" \ + " adc %C0,r1 \n\t" \ + " adc %D0,r1 \n\t" \ + " lsr %D0 \n\t" \ + " ror %C0 \n\t" \ + " ror %B0 \n\t" \ + " ror %A0 \n\t" \ + " ror %1 \n\t" \ + " ldi %2,10 \n\t" \ + " mul %1,%2 \n\t" \ + " mov %1,r1 \n\t" \ + " clr r1 \n\t" \ + : "+r"(in32), "=d"(mod8), "=d"(tmp8) \ + : \ + : "r0") +//------------------------------------------------------------------------------ +/* +// C++ code is based on this version of divmod10 by robtillaart. +// http://forum.arduino.cc/index.php?topic=167414.msg1246851#msg1246851 +// from robtillaart post: +// The code is based upon the divu10() code from the book Hackers Delight1. +// My insight was that the error formula in divu10() was in fact modulo 10 +// but not always. Sometimes it was 10 more. +void divmod10(uint32_t in, uint32_t &div, uint32_t &mod) +{ + // q = in * 0.8; + uint32_t q = (in >> 1) + (in >> 2); + q = q + (q >> 4); + q = q + (q >> 8); + q = q + (q >> 16); // not needed for 16 bit version + + // q = q / 8; ==> q = in *0.1; + q = q >> 3; + + // determine error + uint32_t r = in - ((q << 3) + (q << 1)); // r = in - q*10; + div = q + (r > 9); + if (r > 9) mod = r - 10; + else mod = r; +} +// See: https://github.com/hcs0/Hackers-Delight +// Code below uses 8/10 = 0.1100 1100 1100 1100 1100 1100 1100 1100. +// 15 ops including the multiply, or 17 elementary ops. +unsigned divu10(unsigned n) { + unsigned q, r; + + q = (n >> 1) + (n >> 2); + q = q + (q >> 4); + q = q + (q >> 8); + q = q + (q >> 16); + q = q >> 3; + r = n - q*10; + return q + ((r + 6) >> 4); +// return q + (r > 9); +} +*/ +//------------------------------------------------------------------------------ +// Format 16-bit unsigned +char* fmtBase10(char* str, uint16_t n) { + while (n > 9) { +#ifdef USE_STIMMER + uint8_t tmp8, r; + divmod10_asm16(n, r, tmp8); +#else // USE_STIMMER + uint16_t t = n; + n = (n >> 1) + (n >> 2); + n = n + (n >> 4); + n = n + (n >> 8); + // n = n + (n >> 16); // no code for 16-bit n + n = n >> 3; + uint8_t r = t - (((n << 2) + n) << 1); + // cppcheck wrong. + if (r > 9) { // cppcheck-suppress knownConditionTrueFalse + n++; + r -= 10; + } +#endif // USE_STIMMER + *--str = r + '0'; + } + *--str = n + '0'; + return str; +} +//------------------------------------------------------------------------------ +// format 32-bit unsigned +char* fmtBase10(char* str, uint32_t n) { + while (n > 0XFFFF) { +#ifdef USE_STIMMER + uint8_t tmp8, r; + divmod10_asm32(n, r, tmp8); +#else // USE_STIMMER + uint32_t t = n; + n = (n >> 1) + (n >> 2); + n = n + (n >> 4); + n = n + (n >> 8); + n = n + (n >> 16); + n = n >> 3; + uint8_t r = t - (((n << 2) + n) << 1); + if (r > 9) { + n++; + r -= 10; + } +#endif // USE_STIMMER + *--str = r + '0'; + } + return fmtBase10(str, static_cast(n)); +} +//------------------------------------------------------------------------------ +char* fmtHex(char* str, uint32_t n) { + do { + uint8_t h = n & 0XF; + *--str = h + (h < 10 ? '0' : 'A' - 10); + n >>= 4; + } while (n); + return str; +} +//------------------------------------------------------------------------------ +char* fmtSigned(char* str, int32_t num, uint8_t base, bool caps) { + bool neg = base == 10 && num < 0; + if (neg) { + num = -num; + } + str = fmtUnsigned(str, num, base, caps); + if (neg) { + *--str = '-'; + } + return str; +} +//----------------------------------------------------------------------------- +char* fmtUnsigned(char* str, uint32_t num, uint8_t base, bool caps) { +#if USE_FMT_BASE10 + if (base == 10) return fmtBase10(str, static_cast(num)); +#endif // USE_FMT_BASE10 + do { + int c = num % base; + *--str = c + (c < 10 ? '0' : caps ? 'A' - 10 : 'a' - 10); + } while (num /= base); + return str; +} +//----------------------------------------------------------------------------- + +static const double powTen[] = {1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9}; +static const double rnd[] = {5e-1, 5e-2, 5e-3, 5e-4, 5e-5, + 5e-6, 5e-7, 5e-8, 5e-9, 5e-10}; +static const size_t MAX_PREC = sizeof(powTen) / sizeof(powTen[0]); + +char* fmtDouble(char* str, double num, uint8_t prec, bool altFmt) { + bool neg = num < 0; + if (neg) { + num = -num; + } + if (isnan(num)) { + *--str = 'n'; + *--str = 'a'; + *--str = 'n'; + return str; + } + if (isinf(num)) { + *--str = 'f'; + *--str = 'n'; + *--str = 'i'; + return str; + } + // last float < 2^32 + if (num > 4294967040.0) { + *--str = 'f'; + *--str = 'v'; + *--str = 'o'; + return str; + } + + if (prec > MAX_PREC) { + prec = MAX_PREC; + } + num += rnd[prec]; + uint32_t ul = num; + if (prec) { + char* s = str - prec; + uint32_t f = (num - ul) * powTen[prec - 1]; + str = fmtBase10(str, f); + while (str > s) { + *--str = '0'; + } + } + if (prec || altFmt) { + *--str = '.'; + } + str = fmtBase10(str, ul); + if (neg) { + *--str = '-'; + } + return str; +} +//------------------------------------------------------------------------------ +/** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] ptr Pointer to last char in buffer. + * \param[in] prec Number of digits after decimal point. + * \param[in] expChar Use exp format if non zero. + * \return Pointer to first character of result. + */ +char* fmtDouble(char* str, double value, uint8_t prec, bool altFmt, + char expChar) { + if (expChar != 'e' && expChar != 'E') { + expChar = 0; + } + bool neg = value < 0; + if (neg) { + value = -value; + } + // check for nan inf ovf + if (isnan(value)) { + *--str = 'n'; + *--str = 'a'; + *--str = 'n'; + return str; + } + if (isinf(value)) { + *--str = 'f'; + *--str = 'n'; + *--str = 'i'; + return str; + } + if (!expChar && value > 4294967040.0) { + *--str = 'f'; + *--str = 'v'; + *--str = 'o'; + return str; + } + if (prec > 9) { + prec = 9; + } + if (expChar) { + int8_t exponet = 0; + bool expNeg = false; + if (value) { + if (value > 10.0L) { + while (value > 1e16L) { + value *= 1e-16L; + exponet += 16; + } + while (value > 1e4L) { + value *= 1e-4L; + exponet += 4; + } + while (value > 10.0L) { + value *= 0.1L; + exponet++; + } + } else if (value < 1.0L) { + while (value < 1e-16L) { + value *= 1e16L; + exponet -= 16; + } + while (value < 1e-4L) { + value *= 1e4L; + exponet -= 4; + } + while (value < 1.0L) { + value *= 10.0L; + exponet--; + } + } + value += rnd[prec]; + if (value >= 10.0L) { + value *= 0.1L; + exponet++; + } + expNeg = exponet < 0; + if (expNeg) { + exponet = -exponet; + } + } + str = fmtBase10(str, static_cast(exponet)); + if (exponet < 10) { + *--str = '0'; + } + *--str = expNeg ? '-' : '+'; + *--str = expChar; + } else { + // round value + value += rnd[prec]; + } + + uint32_t whole = value; + if (prec) { + char* tmp = str - prec; + uint32_t fraction = (value - whole) * powTen[prec - 1]; + str = fmtBase10(str, fraction); + while (str > tmp) { + *--str = '0'; + } + } + if (prec || altFmt) *--str = '.'; + str = fmtBase10(str, whole); + if (neg) { + *--str = '-'; + } + return str; +} +//============================================================================== +// functions below not used +//------------------------------------------------------------------------------ +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#ifdef __AVR__ +static const float m[] PROGMEM = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32}; +static const float p[] PROGMEM = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32}; +#else // __AVR__ +static const float m[] = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32}; +static const float p[] = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32}; +#endif // __AVR__ +#endif // DOXYGEN_SHOULD_SKIP_THIS +// scale float v by power of ten. return v*10^n +float scale10(float v, int8_t n) { + const float* s; + if (n < 0) { + n = -n; + s = m; + } else { + s = p; + } + n &= 63; + for (uint8_t i = 0; n; n >>= 1, i++) { +#ifdef __AVR__ + if (n & 1) { + v *= pgm_read_float(&s[i]); + } +#else // __AVR__ + if (n & 1) { + v *= s[i]; + } +#endif // __AVR__ + } + return v; +} +//------------------------------------------------------------------------------ +float scanFloat(const char* str, const char** ptr) { + int16_t const EXP_LIMIT = 100; + bool digit = false; + bool dot = false; + uint32_t fract = 0; + int fracExp = 0; + uint8_t nd = 0; + bool neg; + int c; + float v; + const char* successPtr = str; + + if (ptr) { + *ptr = str; + } + + while (isSpace((c = *str++))) { + } + neg = c == '-'; + if (c == '-' || c == '+') { + c = *str++; + } + // Skip leading zeros + while (c == '0') { + c = *str++; + digit = true; + } + for (;;) { + if (isDigit(c)) { + digit = true; + if (nd < 9) { + fract = 10 * fract + c - '0'; + nd++; + if (dot) { + fracExp--; + } + } else { + if (!dot) { + fracExp++; + } + } + } else if (c == '.') { + if (dot) { + goto fail; + } + dot = true; + } else { + if (!digit) { + goto fail; + } + break; + } + successPtr = str; + c = *str++; + } + if (c == 'e' || c == 'E') { + int exponet = 0; + c = *str++; + bool expNeg = c == '-'; + if (c == '-' || c == '+') { + c = *str++; + } + while (isDigit(c)) { + if (exponet > EXP_LIMIT) { + goto fail; + } + exponet = 10 * exponet + c - '0'; + successPtr = str; + c = *str++; + } + fracExp += expNeg ? -exponet : exponet; + } + if (ptr) { + *ptr = successPtr; + } + v = scale10(static_cast(fract), fracExp); + return neg ? -v : v; + +fail: + return 0; +} diff --git a/third_party/sdfat/src/common/FmtNumber.h b/third_party/sdfat/src/common/FmtNumber.h new file mode 100644 index 00000000..7ba74085 --- /dev/null +++ b/third_party/sdfat/src/common/FmtNumber.h @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include +#include +#include +inline bool isDigit(char c) { return '0' <= (c) && (c) <= '9'; } +inline bool isSpace(char c) { return (c) == ' ' || (0X9 <= (c) && (c) <= 0XD); } +char* fmtBase10(char* str, uint16_t n); +char* fmtBase10(char* str, uint32_t n); +char* fmtDouble(char* str, double d, uint8_t prec, bool altFmt); +char* fmtDouble(char* str, double d, uint8_t prec, bool altFmt, char expChar); +char* fmtHex(char* str, uint32_t n); +char* fmtSigned(char* str, int32_t n, uint8_t base, bool caps); +char* fmtUnsigned(char* str, uint32_t n, uint8_t base, bool caps); diff --git a/third_party/sdfat/src/common/FsApiConstants.h b/third_party/sdfat/src/common/FsApiConstants.h new file mode 100644 index 00000000..e52ba0fd --- /dev/null +++ b/third_party/sdfat/src/common/FsApiConstants.h @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "SysCall.h" +#if USE_FCNTL_H +#include +/* values for GNU Arm Embedded Toolchain. + * O_RDONLY: 0x0 + * O_WRONLY: 0x1 + * O_RDWR: 0x2 + * O_ACCMODE: 0x3 + * O_APPEND: 0x8 + * O_CREAT: 0x200 + * O_TRUNC: 0x400 + * O_EXCL: 0x800 + * O_SYNC: 0x2000 + * O_NONBLOCK: 0x4000 + */ +/** Use O_NONBLOCK for open at EOF */ +#define O_AT_END O_NONBLOCK ///< Open at EOF. +typedef int oflag_t; +#else // USE_FCNTL_H +#define O_RDONLY 0X00 ///< Open for reading only. +#define O_WRONLY 0X01 ///< Open for writing only. +#define O_RDWR 0X02 ///< Open for reading and writing. +#define O_AT_END 0X04 ///< Open at EOF. +#define O_APPEND 0X08 ///< Set append mode. +#define O_CREAT 0x10 ///< Create file if it does not exist. +#define O_TRUNC 0x20 ///< Truncate file to zero length. +#define O_EXCL 0x40 ///< Fail if the file exists. +#define O_SYNC 0x80 ///< Synchronized write I/O operations. + +#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) ///< Mask for access mode. +typedef uint8_t oflag_t; +#endif // USE_FCNTL_H + +#define O_READ O_RDONLY +#define O_WRITE O_WRONLY + +inline bool isWriteMode(oflag_t oflag) { + oflag &= O_ACCMODE; + return oflag == O_WRONLY || oflag == O_RDWR; +} + +// flags for ls() +/** ls() flag for list all files including hidden. */ +const uint8_t LS_A = 1; +/** ls() flag to print modify. date */ +const uint8_t LS_DATE = 2; +/** ls() flag to print file size. */ +const uint8_t LS_SIZE = 4; +/** ls() flag for recursive list of subdirectories */ +const uint8_t LS_R = 8; + +// flags for time-stamp +/** set the file's last access date */ +const uint8_t T_ACCESS = 1; +/** set the file's creation date and time */ +const uint8_t T_CREATE = 2; +/** Set the file's write date and time */ +const uint8_t T_WRITE = 4; diff --git a/third_party/sdfat/src/common/FsBlockDevice.h b/third_party/sdfat/src/common/FsBlockDevice.h new file mode 100644 index 00000000..63e9151a --- /dev/null +++ b/third_party/sdfat/src/common/FsBlockDevice.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +//------------------------------------------------------------------------------ +#if USE_BLOCK_DEVICE_INTERFACE +#include "FsBlockDeviceInterface.h" +typedef FsBlockDeviceInterface FsBlockDevice; +#elif HAS_SDIO_CLASS +#include "SdCard/SdCard.h" +typedef FsBlockDeviceInterface FsBlockDevice; +#else +#include "SdCard/SdCard.h" +typedef SdCard FsBlockDevice; +#endif diff --git a/third_party/sdfat/src/common/FsBlockDeviceInterface.h b/third_party/sdfat/src/common/FsBlockDeviceInterface.h new file mode 100644 index 00000000..6030eb3c --- /dev/null +++ b/third_party/sdfat/src/common/FsBlockDeviceInterface.h @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief FsBlockDeviceInterface include file. + */ +#pragma once +#include "SysCall.h" + +#include +#include +/** + * \class FsBlockDeviceInterface + * \brief FsBlockDeviceInterface class. + */ +class FsBlockDeviceInterface +{ + public: + virtual ~FsBlockDeviceInterface() {} + + /** end use of device */ + virtual void end() {} + /** + * Check for FsBlockDevice busy. + * + * \return true if busy else false. + */ + virtual bool isBusy() = 0; + /** + * Read a sector. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + virtual bool readSector(Sector_t sector, uint8_t* dst) = 0; + + /** + * Read multiple sectors. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + virtual bool readSectors(Sector_t sector, uint8_t* dst, size_t ns) = 0; + + /** \return device size in sectors. */ + virtual Sector_t sectorCount() = 0; + + /** End multi-sector transfer and go to idle state. + * \return true for success or false for failure. + */ + virtual bool syncDevice() = 0; + + /** + * Writes a sector. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + virtual bool writeSector(Sector_t sector, const uint8_t* src) = 0; + + /** + * Write multiple sectors. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + virtual bool writeSectors(Sector_t sector, const uint8_t* src, size_t ns) = 0; +}; diff --git a/third_party/sdfat/src/common/FsCache.cpp b/third_party/sdfat/src/common/FsCache.cpp new file mode 100644 index 00000000..7997f1b3 --- /dev/null +++ b/third_party/sdfat/src/common/FsCache.cpp @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FsCache.cpp" +#include "FsCache.h" + +#include "DebugMacros.h" +//------------------------------------------------------------------------------ +uint8_t* FsCache::prepare(Sector_t sector, uint8_t option) { + if (!m_blockDev) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_sector != sector) { + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!(option & CACHE_OPTION_NO_READ)) { + if (!m_blockDev->readSector(sector, m_buffer)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_status = 0; + m_sector = sector; + } + m_status |= option & CACHE_STATUS_MASK; + return m_buffer; + +fail: + return nullptr; +} +//------------------------------------------------------------------------------ +bool FsCache::sync() { + if (m_status & CACHE_STATUS_DIRTY) { + if (!m_blockDev->writeSector(m_sector, m_buffer)) { + DBG_FAIL_MACRO; + goto fail; + } + // mirror second FAT + if (m_status & CACHE_STATUS_MIRROR_FAT) { + if (!m_blockDev->writeSector(m_sector + m_mirrorOffset, m_buffer)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_status &= ~CACHE_STATUS_DIRTY; + } + return true; + +fail: + return false; +} diff --git a/third_party/sdfat/src/common/FsCache.h b/third_party/sdfat/src/common/FsCache.h new file mode 100644 index 00000000..9f9358ca --- /dev/null +++ b/third_party/sdfat/src/common/FsCache.h @@ -0,0 +1,174 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief Common cache code for exFAT and FAT. + */ +#include "FsBlockDevice.h" +#include "SysCall.h" +/** + * \class FsCache + * \brief Sector cache. + */ +class FsCache { + public: + /** Cached sector is dirty */ + static const uint8_t CACHE_STATUS_DIRTY = 1; + /** Cashed sector is FAT entry and must be mirrored in second FAT. */ + static const uint8_t CACHE_STATUS_MIRROR_FAT = 2; + /** Cache sector status bits */ + static const uint8_t CACHE_STATUS_MASK = + CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT; + /** Sync existing sector but do not read new sector. */ + static const uint8_t CACHE_OPTION_NO_READ = 4; + /** Cache sector for read. */ + static const uint8_t CACHE_FOR_READ = 0; + /** Cache sector for write. */ + static const uint8_t CACHE_FOR_WRITE = CACHE_STATUS_DIRTY; + /** Reserve cache sector for write - do not read from sector device. */ + static const uint8_t CACHE_RESERVE_FOR_WRITE = + CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ; + //---------------------------------------------------------------------------- + /** Constructor. */ + FsCache() { init(nullptr); } // cppcheck-suppress uninitMemberVar + /** \return Cache buffer address. */ + uint8_t* cacheBuffer() { return m_buffer; } + /** + * Cache safe read of a sector. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool cacheSafeRead(Sector_t sector, uint8_t* dst) { + if (isCached(sector)) { + memcpy(dst, m_buffer, 512); + return true; + } + return m_blockDev->readSector(sector, dst); + } + /** + * Cache safe read of multiple sectors. + * + * \param[in] sector Logical sector to be read. + * \param[in] count Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool cacheSafeRead(Sector_t sector, uint8_t* dst, size_t count) { + if (isCached(sector, count) && !sync()) { + return false; + } + return m_blockDev->readSectors(sector, dst, count); + } + /** + * Cache safe write of a sectors. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool cacheSafeWrite(Sector_t sector, const uint8_t* src) { + if (isCached(sector)) { + invalidate(); + } + return m_blockDev->writeSector(sector, src); + } + /** + * Cache safe write of multiple sectors. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \param[in] count Number of sectors to be written. + * \return true for success or false for failure. + */ + bool cacheSafeWrite(Sector_t sector, const uint8_t* src, size_t count) { + if (isCached(sector, count)) { + invalidate(); + } + return m_blockDev->writeSectors(sector, src, count); + } + /** \return Clear the cache and returns a pointer to the cache. */ + uint8_t* clear() { + if (isDirty() && !sync()) { + return nullptr; + } + invalidate(); + return m_buffer; + } + /** Set current sector dirty. */ + void dirty() { m_status |= CACHE_STATUS_DIRTY; } + /** Initialize the cache. + * \param[in] blockDev Block device for this cache. + */ + void init(FsBlockDevice* blockDev) { + m_blockDev = blockDev; + invalidate(); + } + /** Invalidate current cache sector. */ + void invalidate() { + m_status = 0; + m_sector = 0XFFFFFFFF; + } + /** Check if a sector is in the cache. + * \param[in] sector Sector to checked. + * \return true if the sector is cached. + */ + bool isCached(Sector_t sector) const { return sector == m_sector; } + /** Check if the cache contains a sector from a range. + * \param[in] sector Start sector of the range. + * \param[in] count Number of sectors in the range. + * \return true if a sector in the range is cached. + */ + bool isCached(Sector_t sector, size_t count) { + return sector <= m_sector && m_sector < (sector + count); + } + /** \return dirty status */ + bool isDirty() { return m_status & CACHE_STATUS_DIRTY; } + /** Prepare cache to access sector. + * \param[in] sector Sector to read. + * \param[in] option mode for cached sector. + * \return Address of cached sector. + */ + uint8_t* prepare(Sector_t sector, uint8_t option); + /** \return Logical sector number for cached sector. */ + Sector_t sector() { return m_sector; } + /** Set the offset to the second FAT for mirroring. + * \param[in] offset Sector offset to second FAT. + */ + void setMirrorOffset(uint32_t offset) { m_mirrorOffset = offset; } + /** Write current sector if dirty. + * \return true for success or false for failure. + */ + bool sync(); + + private: + uint8_t m_status; + FsBlockDevice* m_blockDev; + Sector_t m_sector; + uint32_t m_mirrorOffset; + uint8_t m_buffer[512] __attribute__((aligned(4))); +}; diff --git a/third_party/sdfat/src/common/FsDateTime.cpp b/third_party/sdfat/src/common/FsDateTime.cpp new file mode 100644 index 00000000..f81ef13b --- /dev/null +++ b/third_party/sdfat/src/common/FsDateTime.cpp @@ -0,0 +1,174 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsDateTime.h" + +#include "FmtNumber.h" +#include "SysCall.h" + +static void dateTimeMs10(uint16_t* date, uint16_t* time, uint8_t* ms10) { + *ms10 = 0; + FsDateTime::callback2(date, time); +} +//------------------------------------------------------------------------------ +/** Date time callback. */ +namespace FsDateTime { +void (*callback)(uint16_t* date, uint16_t* time, uint8_t* ms10) = nullptr; +void (*callback2)(uint16_t* date, uint16_t* time) = nullptr; +void clearCallback() { callback = nullptr; } +void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time)) { + callback = dateTimeMs10; + callback2 = dateTime; +} +void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time, + uint8_t* ms10)) { + callback = dateTime; +} +} // namespace FsDateTime +//------------------------------------------------------------------------------ +static char* fsFmtField(char* str, uint16_t n, char sep) { + if (sep) { + *--str = sep; + } + str = fmtBase10(str, n); + if (n < 10) { + *--str = '0'; + } + return str; +} +//------------------------------------------------------------------------------ +char* fsFmtDate(char* str, uint16_t date) { + str = fsFmtField(str, date & 31, 0); + date >>= 5; + str = fsFmtField(str, date & 15, '-'); + date >>= 4; + return fsFmtField(str, 1980 + date, '-'); +} +//------------------------------------------------------------------------------ +char* fsFmtTime(char* str, uint16_t time) { + time >>= 5; + str = fsFmtField(str, time & 63, 0); + return fsFmtField(str, time >> 6, ':'); +} +//------------------------------------------------------------------------------ +char* fsFmtTime(char* str, uint16_t time, uint8_t sec100) { + str = fsFmtField(str, 2 * (time & 31) + (sec100 < 100 ? 0 : 1), 0); + *--str = ':'; + return fsFmtTime(str, time); +} +//------------------------------------------------------------------------------ +char* fsFmtTimeZone(char* str, int8_t tz) { + if (tz & 0X80) { + char sign; + if (tz & 0X40) { + sign = '-'; + tz = -tz; + } else { + sign = '+'; + tz &= 0X7F; + } + if (tz) { + str = fsFmtField(str, 15 * (tz % 4), 0); + str = fsFmtField(str, tz / 4, ':'); + *--str = sign; + } + *--str = 'C'; + *--str = 'T'; + *--str = 'U'; + } + return str; +} +//------------------------------------------------------------------------------ +size_t fsPrintDate(print_t* pr, uint16_t date) { + // Allow YYYY-MM-DD + char buf[sizeof("YYYY-MM-DD") - 1]; + char* str = buf + sizeof(buf); + if (date) { + str = fsFmtDate(str, date); + } else { + do { + *--str = ' '; + } while (str > buf); + } + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintDateTime(print_t* pr, uint16_t date, uint16_t time) { + // Allow YYYY-MM-DD hh:mm + char buf[sizeof("YYYY-MM-DD hh:mm") - 1]; + char* str = buf + sizeof(buf); + if (date) { + str = fsFmtTime(str, time); + *--str = ' '; + str = fsFmtDate(str, date); + } else { + do { + *--str = ' '; + } while (str > buf); + } + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintDateTime(print_t* pr, uint32_t dateTime) { + return fsPrintDateTime(pr, dateTime >> 16, dateTime & 0XFFFF); +} +//------------------------------------------------------------------------------ +size_t fsPrintDateTime(print_t* pr, uint32_t dateTime, uint8_t s100, + int8_t tz) { + // Allow YYYY-MM-DD hh:mm:ss UTC+hh:mm + char buf[sizeof("YYYY-MM-DD hh:mm:ss UTC+hh:mm") - 1]; + char* str = buf + sizeof(buf); + if (tz) { + str = fsFmtTimeZone(str, tz); + *--str = ' '; + } + str = fsFmtTime(str, static_cast(dateTime), s100); + *--str = ' '; + str = fsFmtDate(str, static_cast(dateTime >> 16)); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintTime(print_t* pr, uint16_t time) { + // Allow hh:mm + char buf[sizeof("hh:mm") - 1]; + char* str = buf + sizeof(buf); + str = fsFmtTime(str, time); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintTime(print_t* pr, uint16_t time, uint8_t sec100) { + // Allow hh:mm:ss + char buf[sizeof("hh:mm:ss") - 1]; + char* str = buf + sizeof(buf); + str = fsFmtTime(str, time, sec100); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintTimeZone(print_t* pr, int8_t tz) { + // Allow UTC+hh:mm + char buf[sizeof("UTC+hh:mm") - 1]; + char* str = buf + sizeof(buf); + str = fsFmtTimeZone(str, tz); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} diff --git a/third_party/sdfat/src/common/FsDateTime.h b/third_party/sdfat/src/common/FsDateTime.h new file mode 100644 index 00000000..ee820025 --- /dev/null +++ b/third_party/sdfat/src/common/FsDateTime.h @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include + +#include "CompileDateTime.h" +#include "SysCall.h" + +/** Backward compatible definition. */ +#define FAT_DATE(y, m, d) FS_DATE(y, m, d) + +/** Backward compatible definition. */ +#define FAT_TIME(h, m, s) FS_TIME(h, m, s) + +/** Date time callback */ +namespace FsDateTime { +/** Date time callback. */ +extern void (*callback)(uint16_t* date, uint16_t* time, uint8_t* ms10); +/** Date time callback. */ +extern void (*callback2)(uint16_t* date, uint16_t* time); +/** Cancel callback. */ +void clearCallback(); +/** Set the date/time callback function. + * + * \param[in] dateTime The user's call back function. The callback. + * function is of the form: + * + * \code + * void dateTime(uint16_t* date, uint16_t* time) { + * uint16_t year; + * uint8_t month, day, hour, minute, second; + * + * // User gets date and time from GPS or real-time clock here. + * + * // Return date using FS_DATE macro to format fields. + * *date = FS_DATE(year, month, day); + * + * // Return time using FS_TIME macro to format fields. + * *time = FS_TIME(hour, minute, second); + * } + * \endcode + * + * Sets the function that is called when a file is created or when + * a file's directory entry is modified by sync(). All timestamps, + * access, creation, and modify, are set when a file is created. + * sync() maintains the last access date and last modify date/time. + * + */ +void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time)); +/** Set the date/time callback function. + * + * \param[in] dateTime The user's call back function. The callback + * function is of the form: + * + * \code + * void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) { + * uint16_t year; + * uint8_t month, day, hour, minute, second; + * + * // User gets date and time from GPS or real-time clock here. + * + * // Return date using FS_DATE macro to format fields + * *date = FS_DATE(year, month, day); + * + * // Return time using FS_TIME macro to format fields + * *time = FS_TIME(hour, minute, second); + * + * // Return the time since the last even second in units of 10 ms. + * // The granularity of the seconds part of FS_TIME is 2 seconds so + * // this field is a count of hundredth of a second and its valid + * // range is 0-199 inclusive. + * // For a simple RTC return 100*(seconds & 1). + * *ms10 = + * } + * \endcode + * + * Sets the function that is called when a file is created or when + * a file's directory entry is modified by sync(). All timestamps, + * access, creation, and modify, are set when a file is created. + * sync() maintains the last access date and last modify date/time. + * + */ +void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time, + uint8_t* ms10)); +} // namespace FsDateTime + +/** date field for directory entry + * \param[in] year [1980,2107] + * \param[in] month [1,12] + * \param[in] day [1,31] + * + * \return Packed date for directory entry. + */ +static inline uint16_t FS_DATE(uint16_t year, uint8_t month, uint8_t day) { + year -= 1980; + return year > 127 || month > 12 || day > 31 ? 0 + : year << 9 | month << 5 | day; +} +/** year part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted year [1980,2107] + */ +static inline uint16_t FS_YEAR(uint16_t fatDate) { + return 1980 + (fatDate >> 9); +} +/** month part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted month [1,12] + */ +static inline uint8_t FS_MONTH(uint16_t fatDate) { + return (fatDate >> 5) & 0XF; +} +/** day part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted day [1,31] + */ +static inline uint8_t FS_DAY(uint16_t fatDate) { return fatDate & 0X1F; } +/** time field for directory entry + * \param[in] hour [0,23] + * \param[in] minute [0,59] + * \param[in] second [0,59] + * + * \return Packed time for directory entry. + */ +static inline uint16_t FS_TIME(uint8_t hour, uint8_t minute, uint8_t second) { + return hour > 23 || minute > 59 || second > 59 + ? 0 + : hour << 11 | minute << 5 | second >> 1; +} +/** hour part of FAT directory time field + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted hour [0,23] + */ +static inline uint8_t FS_HOUR(uint16_t fatTime) { return fatTime >> 11; } +/** minute part of FAT directory time field + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted minute [0,59] + */ +static inline uint8_t FS_MINUTE(uint16_t fatTime) { + return (fatTime >> 5) & 0X3F; +} +/** second part of FAT directory time field + * N\note second/2 is stored in packed time. + * + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted second [0,58] + */ +static inline uint8_t FS_SECOND(uint16_t fatTime) { + return 2 * (fatTime & 0X1F); +} +char* fsFmtDate(char* str, uint16_t date); +char* fsFmtTime(char* str, uint16_t time); +char* fsFmtTime(char* str, uint16_t time, uint8_t sec100); +char* fsFmtTimeZone(char* str, int8_t tz); +size_t fsPrintDate(print_t* pr, uint16_t date); +size_t fsPrintDateTime(print_t* pr, uint16_t date, uint16_t time); +size_t fsPrintDateTime(print_t* pr, uint32_t dateTime); +size_t fsPrintDateTime(print_t* pr, uint32_t dateTime, uint8_t s100, int8_t tz); +size_t fsPrintTime(print_t* pr, uint16_t time); +size_t fsPrintTime(print_t* pr, uint16_t time, uint8_t sec100); +size_t fsPrintTimeZone(print_t* pr, int8_t tz); diff --git a/third_party/sdfat/src/common/FsName.cpp b/third_party/sdfat/src/common/FsName.cpp new file mode 100644 index 00000000..e16d74b4 --- /dev/null +++ b/third_party/sdfat/src/common/FsName.cpp @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsName.h" + +#include "FsUtf.h" +#if USE_UTF8_LONG_NAMES +uint16_t FsName::get16() { + uint16_t rtn; + if (ls) { + rtn = ls; + ls = 0; + } else if (next >= end) { + rtn = 0; + } else { + uint32_t cp; + const char* ptr = FsUtf::mbToCp(next, end, &cp); + if (!ptr) { + goto fail; + } + next = ptr; + if (cp <= 0XFFFF) { + rtn = cp; + } else { + ls = FsUtf::lowSurrogate(cp); + rtn = FsUtf::highSurrogate(cp); + } + } + return rtn; + +fail: + return 0XFFFF; +} +#endif // USE_UTF8_LONG_NAMES diff --git a/third_party/sdfat/src/common/FsName.h b/third_party/sdfat/src/common/FsName.h new file mode 100644 index 00000000..40906afc --- /dev/null +++ b/third_party/sdfat/src/common/FsName.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include + +#include "SysCall.h" +/** + * \file + * \brief FsName class. + */ +/** + * \class FsName + * \brief Handle UTF-8 file names. + */ +class FsName { + public: + /** Beginning of LFN. */ + const char* begin; + /** Next LFN character of end. */ + const char* next; + /** Position one beyond last LFN character. */ + const char* end; +#if !USE_UTF8_LONG_NAMES + /** \return true if at end. */ + bool atEnd() { return next == end; } + /** Reset to start of LFN. */ + void reset() { next = begin; } + /** \return next char of LFN. */ + char getch() { return atEnd() ? 0 : *next++; } + /** \return next UTF-16 unit of LFN. */ + uint16_t get16() { return atEnd() ? 0 : *next++; } +#else // !USE_UTF8_LONG_NAMES + uint16_t ls = 0; + bool atEnd() { return !ls && next == end; } + void reset() { + next = begin; + ls = 0; // lowSurrogate + } + uint16_t get16(); +#endif // !USE_UTF8_LONG_NAMES +}; diff --git a/third_party/sdfat/src/common/FsStructs.cpp b/third_party/sdfat/src/common/FsStructs.cpp new file mode 100644 index 00000000..9d96b0f1 --- /dev/null +++ b/third_party/sdfat/src/common/FsStructs.cpp @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsStructs.h" +// bgnLba = relSector; +// endLba = relSector + partSize - 1; +void lbaToMbrChs(uint8_t* chs, uint32_t capacityMB, uint32_t lba) { + uint32_t c; + uint8_t h; + uint8_t s; + + uint8_t numberOfHeads; + uint8_t sectorsPerTrack = capacityMB <= 256 ? 32 : 63; + if (capacityMB <= 16) { + numberOfHeads = 2; + } else if (capacityMB <= 32) { + numberOfHeads = 4; + } else if (capacityMB <= 128) { + numberOfHeads = 8; + } else if (capacityMB <= 504) { + numberOfHeads = 16; + } else if (capacityMB <= 1008) { + numberOfHeads = 32; + } else if (capacityMB <= 2016) { + numberOfHeads = 64; + } else if (capacityMB <= 4032) { + numberOfHeads = 128; + } else { + numberOfHeads = 255; + } + c = lba / (numberOfHeads * sectorsPerTrack); + if (c <= 1023) { + h = (lba % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack; + s = (lba % sectorsPerTrack) + 1; + } else { + c = 1023; + h = 254; + s = 63; + } + chs[0] = h; + chs[1] = ((c >> 2) & 0XC0) | s; + chs[2] = c; +} diff --git a/third_party/sdfat/src/common/FsStructs.h b/third_party/sdfat/src/common/FsStructs.h new file mode 100644 index 00000000..155064e1 --- /dev/null +++ b/third_party/sdfat/src/common/FsStructs.h @@ -0,0 +1,477 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include +#include +#include +//------------------------------------------------------------------------------ +// See: +// https://learn.microsoft.com/en-us/windows/win32/fileio/file-systems +// https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification +// https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc +// https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/FileIO/exfat-specification.md +// https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html +// https://en.wikipedia.org/wiki/File_Allocation_Table +//------------------------------------------------------------------------------ +typedef uint32_t Cluster_t; +//------------------------------------------------------------------------------ +void lbaToMbrChs(uint8_t* chs, uint32_t capacityMB, uint32_t lba); +//------------------------------------------------------------------------------ +#if !defined(USE_SIMPLE_LITTLE_ENDIAN) || USE_SIMPLE_LITTLE_ENDIAN +// assumes CPU is little-endian and handles alignment issues. +inline uint16_t getLe16(const uint8_t* src) { + return *reinterpret_cast(src); +} +inline uint32_t getLe32(const uint8_t* src) { + return *reinterpret_cast(src); +} +inline uint64_t getLe64(const uint8_t* src) { + return *reinterpret_cast(src); +} +inline void setLe16(uint8_t* dst, uint16_t src) { + *reinterpret_cast(dst) = src; +} +inline void setLe32(uint8_t* dst, uint32_t src) { + *reinterpret_cast(dst) = src; +} +inline void setLe64(uint8_t* dst, uint64_t src) { + *reinterpret_cast(dst) = src; +} +#else // USE_SIMPLE_LITTLE_ENDIAN +inline uint16_t getLe16(const uint8_t* src) { + return static_cast(src[0]) << 0 | static_cast(src[1]) + << 8; +} +inline uint32_t getLe32(const uint8_t* src) { + return static_cast(src[0]) << 0 | + static_cast(src[1]) << 8 | + static_cast(src[2]) << 16 | + static_cast(src[3]) << 24; +} +inline uint64_t getLe64(const uint8_t* src) { + return static_cast(src[0]) << 0 | + static_cast(src[1]) << 8 | + static_cast(src[2]) << 16 | + static_cast(src[3]) << 24 | + static_cast(src[4]) << 32 | + static_cast(src[5]) << 40 | + static_cast(src[7]) << 56; +} +inline void setLe16(uint8_t* dst, uint16_t src) { + dst[0] = src >> 0; + dst[1] = src >> 8; +} +inline void setLe32(uint8_t* dst, uint32_t src) { + dst[0] = src >> 0; + dst[1] = src >> 8; + dst[2] = src >> 16; + dst[3] = src >> 24; +} +inline void setLe64(uint8_t* dst, uint64_t src) { + dst[0] = src >> 0; + dst[1] = src >> 8; + dst[2] = src >> 16; + dst[3] = src >> 24; + dst[4] = src >> 32; + dst[5] = src >> 40; + dst[6] = src >> 48; + dst[7] = src >> 56; +} +#endif // USE_SIMPLE_LITTLE_ENDIAN +//------------------------------------------------------------------------------ +// Size of FAT and exFAT directory structures. +const size_t FS_DIR_SIZE = 32; +//------------------------------------------------------------------------------ +// Reserved characters for exFAT names and FAT LFN. +inline bool lfnReservedChar(uint8_t c) { + return c < 0X20 || c == '"' || c == '*' || c == '/' || c == ':' || c == '<' || + c == '>' || c == '?' || c == '\\' || c == '|'; +} +//------------------------------------------------------------------------------ +// Reserved characters for FAT short 8.3 names. +inline bool sfnReservedChar(uint8_t c) { + if (c == '"' || c == '|' || c == '[' || c == '\\' || c == ']') { + return true; + } + // *+,./ or :;<=>? + if ((0X2A <= c && c <= 0X2F && c != 0X2D) || (0X3A <= c && c <= 0X3F)) { + return true; + } + // Reserved if not in range (0X20, 0X7F). + return !(0X20 < c && c < 0X7F); +} +//------------------------------------------------------------------------------ +const uint16_t MBR_SIGNATURE = 0xAA55; +const uint16_t PBR_SIGNATURE = 0xAA55; +const uint8_t MBR_TYPE_FAT12 = 0x01; // FAT with 12-bit LBA. +const uint8_t MBR_TYPE_FAT16 = 0x04; // Original FAT16 with 16-bit LBA. +const uint8_t MBR_TYPE_FAT16B = 0x06; // Modern FAT16 with 32-bit LBA. +const uint8_t MBR_TYPE_FAT32 = 0x0C; // Modern FAT32 with 32-bit LBA. +const uint8_t MBR_TYPE_EX_FAT = 0x07; // NTFS or exFAT. +const uint8_t MBR_TYPE_GPT = 0xEE; // Protective MBR for GPT disk. +typedef struct mbrPartition { + uint8_t boot; + uint8_t beginCHS[3]; + uint8_t type; + uint8_t endCHS[3]; + uint8_t startSector[4]; + uint8_t totalSectors[4]; +} MbrPart_t; +//------------------------------------------------------------------------------ +typedef struct masterBootRecordSector { + uint8_t bootCode[446]; + MbrPart_t part[4]; + uint8_t signature[2]; +} MbrSector_t; +//------------------------------------------------------------------------------ + +typedef struct GptHeader { + uint8_t signature[8]; // must contain the ASCII string “EFI PART” + uint8_t revision[4]; // The revision number for this header + uint8_t headerSize[4]; // Size in bytes of the GPT Header + uint8_t headerCRC32[4]; // CRC32 checksum for the GPT Header structure. + uint8_t reserved[4]; // Must be zero. + uint8_t myLBA[8]; // The LBA that contains this data structure. + uint8_t alternateLBA[8]; // LBA address of the alternate GPT Header. + uint8_t firstUsableLBA[8]; // The first usable LBA for a partition. + uint8_t lastUsableLBA[8]; // The last usable LBA for a partition. + uint8_t diskGUID[16]; // GUID that uniquely identify the disk. + uint8_t partitionEntryLBA[8]; // Starting LBA of Partition Entry array. + uint8_t numberOfPartitionEntries[4]; // The number of Partition Entries. + uint8_t sizeOfPartitionEntry[4]; // Size of GUID each Partition Entry. + uint8_t partitionEntryArrayCRC32; // PartitionE ntryArrayCRC32. + // The rest of the block is reserved by UEFI and must be zero. + // Verify signature. + bool valid() { return memcmp(signature, "EFI PART", 8) == 0; } +} GptHeader_t; +//------------------------------------------------------------------------------ +// Microsoft basic data partition GUID: EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 +// partitionTypeGUID: A2 A0 D0 EB E5 B9 33 44 87 C0 68 B6 B7 26 99 C7 +static const uint8_t MS_TYPE_GUID[16] = {0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, + 0x33, 0x44, 0x87, 0xC0, 0x68, 0xB6, + 0xB7, 0x26, 0x99, 0xC7}; +typedef struct GptPart { + uint8_t partitionTypeGUID[16]; // Unique ID for partition, zero if unused. + uint8_t uniquePartitionGUID[16]; // GUID that is unique for every entry. + uint8_t startingLBA[8]; // Starting LBA for this entry. + uint8_t endingLBA[8]; // Ending LBA for this entry. + uint8_t attributes[8]; // Attribute bits, all bits reserved by UEFI. + uint8_t partitionName[72]; // Null-terminated string. + // Entry is used. + bool used() { + for (int i = 0; i < 16; i++) { + if (partitionTypeGUID[i]) { + return true; + } + } + return false; + } + // Verify GUID for Microsoft basic data partition. + bool isMsBasicPartition() { + return memcmp(partitionTypeGUID, MS_TYPE_GUID, 16) == 0; + } +} GptPart_t; +//------------------------------------------------------------------------------ +typedef struct partitionBootSector { + uint8_t jmpInstruction[3]; + char oemName[8]; + uint8_t bpb[109]; + uint8_t bootCode[390]; + uint8_t signature[2]; +} pbs_t; +//------------------------------------------------------------------------------ +typedef struct { + uint8_t type; + uint8_t data[31]; +} DirGeneric_t; +//============================================================================== +typedef struct { + uint64_t position; + uint32_t cluster; +} fspos_t; +//============================================================================== +const uint8_t EXTENDED_BOOT_SIGNATURE = 0X29; +typedef struct biosParameterBlockFat16 { + uint8_t bytesPerSector[2]; + uint8_t sectorsPerCluster; + uint8_t reservedSectorCount[2]; + uint8_t fatCount; + uint8_t rootDirEntryCount[2]; + uint8_t totalSectors16[2]; + uint8_t mediaType; + uint8_t sectorsPerFat16[2]; + uint8_t sectorsPerTrtack[2]; + uint8_t headCount[2]; + uint8_t hidddenSectors[4]; + uint8_t totalSectors32[4]; + + uint8_t physicalDriveNumber; + uint8_t extReserved; + uint8_t extSignature; + uint8_t volumeSerialNumber[4]; + uint8_t volumeLabel[11]; + uint8_t volumeType[8]; +} BpbFat16_t; +//------------------------------------------------------------------------------ +typedef struct biosParameterBlockFat32 { + uint8_t bytesPerSector[2]; + uint8_t sectorsPerCluster; + uint8_t reservedSectorCount[2]; + uint8_t fatCount; + uint8_t rootDirEntryCount[2]; + uint8_t totalSectors16[2]; + uint8_t mediaType; + uint8_t sectorsPerFat16[2]; + uint8_t sectorsPerTrtack[2]; + uint8_t headCount[2]; + uint8_t hidddenSectors[4]; + uint8_t totalSectors32[4]; + + uint8_t sectorsPerFat32[4]; + uint8_t fat32Flags[2]; + uint8_t fat32Version[2]; + uint8_t fat32RootCluster[4]; + uint8_t fat32FSInfoSector[2]; + uint8_t fat32BackBootSector[2]; + uint8_t fat32Reserved[12]; + + uint8_t physicalDriveNumber; + uint8_t extReserved; + uint8_t extSignature; + uint8_t volumeSerialNumber[4]; + uint8_t volumeLabel[11]; + uint8_t volumeType[8]; +} BpbFat32_t; +//------------------------------------------------------------------------------ +typedef struct partitionBootSectorFat { + uint8_t jmpInstruction[3]; + char oemName[8]; + union { + uint8_t bpb[109]; + BpbFat16_t bpb16; + BpbFat32_t bpb32; + } bpb; + uint8_t bootCode[390]; + uint8_t signature[2]; +} PbsFat_t; +//------------------------------------------------------------------------------ +const uint32_t FSINFO_LEAD_SIGNATURE = 0X41615252; +const uint32_t FSINFO_STRUCT_SIGNATURE = 0x61417272; +const uint32_t FSINFO_TRAIL_SIGNATURE = 0xAA550000; +typedef struct FsInfoSector { + uint8_t leadSignature[4]; + uint8_t reserved1[480]; + uint8_t structSignature[4]; + uint8_t freeCount[4]; + uint8_t nextFree[4]; + uint8_t reserved2[12]; + uint8_t trailSignature[4]; +} FsInfo_t; +//============================================================================== +/** Attributes common to FAT and exFAT */ +const uint8_t FS_ATTRIB_READ_ONLY = 0x01; +const uint8_t FS_ATTRIB_HIDDEN = 0x02; +const uint8_t FS_ATTRIB_SYSTEM = 0x04; +const uint8_t FS_ATTRIB_DIRECTORY = 0x10; +const uint8_t FS_ATTRIB_ARCHIVE = 0x20; +// Attributes that users can change. +const uint8_t FS_ATTRIB_USER_SETTABLE = FS_ATTRIB_READ_ONLY | FS_ATTRIB_HIDDEN | + FS_ATTRIB_SYSTEM | FS_ATTRIB_ARCHIVE; +// Attributes to copy when a file is opened. +const uint8_t FS_ATTRIB_COPY = FS_ATTRIB_USER_SETTABLE | FS_ATTRIB_DIRECTORY; +//============================================================================== +/** name[0] value for entry that is free and no allocated entries follow */ +const uint8_t FAT_NAME_FREE = 0X00; +/** name[0] value for entry that is free after being "deleted" */ +const uint8_t FAT_NAME_DELETED = 0XE5; +// Directory attribute of volume label. +const uint8_t FAT_ATTRIB_LABEL = 0x08; +const uint8_t FAT_ATTRIB_LONG_NAME = 0X0F; +/** Filename base-name is all lower case */ +const uint8_t FAT_CASE_LC_BASE = 0X08; +/** Filename extension is all lower case.*/ +const uint8_t FAT_CASE_LC_EXT = 0X10; + +typedef struct { + uint8_t name[11]; + uint8_t attributes; + uint8_t caseFlags; + uint8_t createTimeMs; + uint8_t createTime[2]; + uint8_t createDate[2]; + uint8_t accessDate[2]; + uint8_t firstClusterHigh[2]; + uint8_t modifyTime[2]; + uint8_t modifyDate[2]; + uint8_t firstClusterLow[2]; + uint8_t fileSize[4]; +} DirFat_t; + +static inline bool isFatFile(const DirFat_t* dir) { + return (dir->attributes & (FS_ATTRIB_DIRECTORY | FAT_ATTRIB_LABEL)) == 0; +} +static inline bool isFatFileOrSubdir(const DirFat_t* dir) { + return (dir->attributes & FAT_ATTRIB_LABEL) == 0; +} +static inline uint8_t isFatLongName(const DirFat_t* dir) { + return dir->attributes == FAT_ATTRIB_LONG_NAME; +} +static inline bool isFatSubdir(const DirFat_t* dir) { + return (dir->attributes & (FS_ATTRIB_DIRECTORY | FAT_ATTRIB_LABEL)) == + FS_ATTRIB_DIRECTORY; +} +//------------------------------------------------------------------------------ +/** + * Order mask that indicates the entry is the last long dir entry in a + * set of long dir entries. All valid sets of long dir entries must + * begin with an entry having this mask. + */ +const uint8_t FAT_ORDER_LAST_LONG_ENTRY = 0X40; +/** Max long file name length */ + +const uint8_t FAT_MAX_LFN_LENGTH = 255; +typedef struct { + uint8_t order; + uint8_t unicode1[10]; + uint8_t attributes; + uint8_t mustBeZero1; + uint8_t checksum; + uint8_t unicode2[12]; + uint8_t mustBeZero2[2]; + uint8_t unicode3[4]; +} DirLfn_t; +//============================================================================== +inline uint32_t exFatChecksum(uint32_t sum, uint8_t data) { + return (sum << 31) + (sum >> 1) + data; +} +//------------------------------------------------------------------------------ +typedef struct biosParameterBlockExFat { + uint8_t mustBeZero[53]; + uint8_t partitionOffset[8]; + uint8_t volumeLength[8]; + uint8_t fatOffset[4]; + uint8_t fatLength[4]; + uint8_t clusterHeapOffset[4]; + uint8_t clusterCount[4]; + uint8_t rootDirectoryCluster[4]; + uint8_t volumeSerialNumber[4]; + uint8_t fileSystemRevision[2]; + uint8_t volumeFlags[2]; + uint8_t bytesPerSectorShift; + uint8_t sectorsPerClusterShift; + uint8_t numberOfFats; + uint8_t driveSelect; + uint8_t percentInUse; + uint8_t reserved[7]; +} BpbExFat_t; +//------------------------------------------------------------------------------ +typedef struct ExFatBootSector { + uint8_t jmpInstruction[3]; + char oemName[8]; + BpbExFat_t bpb; + uint8_t bootCode[390]; + uint8_t signature[2]; +} ExFatPbs_t; +//------------------------------------------------------------------------------ +const uint32_t EXFAT_EOC = 0XFFFFFFFF; + +const uint8_t EXFAT_TYPE_BITMAP = 0X81; +typedef struct { + uint8_t type; + uint8_t flags; + uint8_t reserved[18]; + uint8_t firstCluster[4]; + uint8_t size[8]; +} DirBitmap_t; +//------------------------------------------------------------------------------ +const uint8_t EXFAT_TYPE_UPCASE = 0X82; +typedef struct { + uint8_t type; + uint8_t reserved1[3]; + uint8_t checksum[4]; + uint8_t reserved2[12]; + uint8_t firstCluster[4]; + uint8_t size[8]; +} DirUpcase_t; +//------------------------------------------------------------------------------ +const uint8_t EXFAT_TYPE_LABEL = 0X83; +typedef struct { + uint8_t type; + uint8_t labelLength; + uint8_t unicode[22]; + uint8_t reserved[8]; +} DirLabel_t; +//------------------------------------------------------------------------------ +// Last entry in directory. +const uint8_t EXFAT_TYPE_END_DIR = 0X00; +// Entry is used if bit is set. +const uint8_t EXFAT_TYPE_USED = 0X80; +const uint8_t EXFAT_TYPE_FILE = 0X85; +// File attribute reserved since used for FAT volume label. +const uint8_t EXFAT_ATTRIB_RESERVED = 0x08; + +typedef struct { + uint8_t type; + uint8_t setCount; + uint8_t setChecksum[2]; + uint8_t attributes[2]; + uint8_t reserved1[2]; + uint8_t createTime[2]; + uint8_t createDate[2]; + uint8_t modifyTime[2]; + uint8_t modifyDate[2]; + uint8_t accessTime[2]; + uint8_t accessDate[2]; + uint8_t createTimeMs; + uint8_t modifyTimeMs; + uint8_t createTimezone; + uint8_t modifyTimezone; + uint8_t accessTimezone; + uint8_t reserved2[7]; +} DirFile_t; + +const uint8_t EXFAT_TYPE_STREAM = 0XC0; +const uint8_t EXFAT_FLAG_ALWAYS1 = 0x01; +const uint8_t EXFAT_FLAG_CONTIGUOUS = 0x02; +typedef struct { + uint8_t type; + uint8_t flags; + uint8_t reserved1; + uint8_t nameLength; + uint8_t nameHash[2]; + uint8_t reserved2[2]; + uint8_t validLength[8]; + uint8_t reserved3[4]; + uint8_t firstCluster[4]; + uint8_t dataLength[8]; +} DirStream_t; + +const uint8_t EXFAT_TYPE_NAME = 0XC1; +const uint8_t EXFAT_MAX_NAME_LENGTH = 255; +typedef struct { + uint8_t type; + uint8_t mustBeZero; + uint8_t unicode[30]; +} DirName_t; diff --git a/third_party/sdfat/src/common/FsUtf.cpp b/third_party/sdfat/src/common/FsUtf.cpp new file mode 100644 index 00000000..85395428 --- /dev/null +++ b/third_party/sdfat/src/common/FsUtf.cpp @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsUtf.h" +namespace FsUtf { +//---------------------------------------------------------------------------- +char* cpToMb(uint32_t cp, char* str, const char* end) { + size_t n = end - str; + if (cp < 0X80) { + if (n < 1) goto fail; + *(str++) = static_cast(cp); + } else if (cp < 0X800) { + if (n < 2) goto fail; + *(str++) = static_cast((cp >> 6) | 0XC0); + *(str++) = static_cast((cp & 0X3F) | 0X80); + } else if (cp < 0X10000) { + if (n < 3) goto fail; + *(str++) = static_cast((cp >> 12) | 0XE0); + *(str++) = static_cast(((cp >> 6) & 0X3F) | 0X80); + *(str++) = static_cast((cp & 0X3F) | 0X80); + } else { + if (n < 4) goto fail; + *(str++) = static_cast((cp >> 18) | 0XF0); + *(str++) = static_cast(((cp >> 12) & 0X3F) | 0X80); + *(str++) = static_cast(((cp >> 6) & 0X3F) | 0X80); + *(str++) = static_cast((cp & 0X3F) | 0X80); + } + return str; + +fail: + return nullptr; +} +//---------------------------------------------------------------------------- +// to do? improve error check +const char* mbToCp(const char* str, const char* end, uint32_t* rtn) { + size_t n; + uint32_t cp; + if (str >= end) { + return nullptr; + } + uint8_t ch = str[0]; + if ((ch & 0X80) == 0) { + *rtn = ch; + return str + 1; + } + if ((ch & 0XE0) == 0XC0) { + cp = ch & 0X1F; + n = 2; + } else if ((ch & 0XF0) == 0XE0) { + cp = ch & 0X0F; + n = 3; + } else if ((ch & 0XF8) == 0XF0) { + cp = ch & 0X07; + n = 4; + } else { + return nullptr; + } + if ((str + n) > end) { + return nullptr; + } + for (size_t i = 1; i < n; i++) { + ch = str[i]; + if ((ch & 0XC0) != 0X80) { + return nullptr; + } + cp <<= 6; + cp |= ch & 0X3F; + } + // Don't allow over long as ASCII. + if (cp < 0X80 || !isValidCp(cp)) { + return nullptr; + } + *rtn = cp; + return str + n; +} +//---------------------------------------------------------------------------- +const char* mbToU16(const char* str, const char* end, uint16_t* hs, + uint16_t* ls) { + uint32_t cp; + const char* ptr = mbToCp(str, end, &cp); + if (!ptr) { + return nullptr; + } + if (cp <= 0XFFFF) { + *hs = cp; + *ls = 0; + } else { + *hs = highSurrogate(cp); + *ls = lowSurrogate(cp); + } + return ptr; +} +} // namespace FsUtf diff --git a/third_party/sdfat/src/common/FsUtf.h b/third_party/sdfat/src/common/FsUtf.h new file mode 100644 index 00000000..ca4d8ccb --- /dev/null +++ b/third_party/sdfat/src/common/FsUtf.h @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief Unicode Transformation Format functions. + */ +#include +#include +namespace FsUtf { +/** High surrogate for a code point. + * \param{in} cp code point. + * \return high surrogate. + */ +inline uint16_t highSurrogate(uint32_t cp) { + return (cp >> 10) + (0XD800 - (0X10000 >> 10)); +} +/** Low surrogate for a code point. + * \param{in} cp code point. + * \return low surrogate. + */ +inline uint16_t lowSurrogate(uint32_t cp) { return (cp & 0X3FF) + 0XDC00; } +/** Check for a valid code point. + * \param[in] cp code point. + * \return true if valid else false. + */ +inline bool isValidCp(uint32_t cp) { + return cp <= 0x10FFFF && (cp < 0XD800 || cp > 0XDFFF); +} +/** Check for UTF-16 surrogate. + * \param[in] c UTF-16 unit. + * \return true if c is a surrogate else false. + */ +inline bool isSurrogate(uint16_t c) { return 0XD800 <= c && c <= 0XDFFF; } +/** Check for UTF-16 high surrogate. + * \param[in] c UTF-16 unit.. + * \return true if c is a high surrogate else false. + */ +inline bool isHighSurrogate(uint16_t c) { return 0XD800 <= c && c <= 0XDBFF; } +/** Check for UTF-16 low surrogate. + * \param[in] c UTF-16 unit.. + * \return true if c is a low surrogate else false. + */ +inline bool isLowSurrogate(uint16_t c) { return 0XDC00 <= c && c <= 0XDFFF; } +/** Convert UFT-16 surrogate pair to code point. + * \param[in] hs high surrogate. + * \param[in] ls low surrogate. + * \return code point. + */ +inline uint32_t u16ToCp(uint16_t hs, uint16_t ls) { + return 0X10000 + (((hs & 0X3FF) << 10) | (ls & 0X3FF)); +} +/** Encodes a 32 bit code point as a UTF-8 sequence. + * \param[in] cp code point to encode. + * \param[out] str location for UTF-8 sequence. + * \param[in] end location following last character of str. + * \return location one beyond last encoded character. + */ +char* cpToMb(uint32_t cp, char* str, const char* end); +/** Get next code point from a UTF-8 sequence. + * \param[in] str location for UTF-8 sequence. + * \param[in] end location following last character of str. + * May be nullptr if str is zero terminated. + * \param[out] rtn location for the code point. + * \return location of next UTF-8 character in str of nullptr for error. + */ +const char* mbToCp(const char* str, const char* end, uint32_t* rtn); +/** Get next code point from a UTF-8 sequence as UTF-16. + * \param[in] str location for UTF-8 sequence. + * \param[in] end location following last character of str. + * \param[out] hs location for the code point or high surrogate. + * \param[out] ls location for zero or high surrogate. + * \return location of next UTF-8 character in str of nullptr for error. + */ +const char* mbToU16(const char* str, const char* end, uint16_t* hs, + uint16_t* ls); +} // namespace FsUtf diff --git a/third_party/sdfat/src/common/PrintBasic.cpp b/third_party/sdfat/src/common/PrintBasic.cpp new file mode 100644 index 00000000..1f7b1f9b --- /dev/null +++ b/third_party/sdfat/src/common/PrintBasic.cpp @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "PrintBasic.h" +#if ENABLE_ARDUINO_FEATURES == 0 +#include + +size_t PrintBasic::print(long n, uint8_t base) { + if (n < 0 && base == 10) { + return print('-') + printNum(-n, base); + } + return printNum(n, base); +} +size_t PrintBasic::printNum(unsigned long n, uint8_t base) { + const uint8_t DIM = 8 * sizeof(long); + char buf[DIM]; + char *str = &buf[DIM]; + + if (base < 2) return 0; + + do { + char c = n % base; + n /= base; + *--str = c + (c < 10 ? '0' : 'A' - 10); + } while (n); + return write(str, &buf[DIM] - str); +} + +size_t PrintBasic::printDouble(double n, uint8_t prec) { + // Max printable 32-bit floating point number. AVR uses 32-bit double. + const double maxfp = static_cast(0XFFFFFF00UL); + size_t rtn = 0; + + if (isnan(n)) { + return write("NaN"); + } + if (n < 0) { + n = -n; + rtn += print('-'); + } + if (isinf(n)) { + return rtn + write("Inf"); + } + if (n > maxfp) { + return rtn + write("Ovf"); + } + + double round = 0.5; + for (uint8_t i = 0; i < prec; ++i) { + round *= 0.1; + } + + n += round; + + uint32_t whole = (uint32_t)n; + rtn += print(whole); + + if (prec) { + rtn += print('.'); + double fraction = n - static_cast(whole); + for (uint8_t i = 0; i < prec; i++) { + fraction *= 10.0; + uint8_t digit = fraction; + rtn += print(digit); + fraction -= digit; + } + } + return rtn; +} +#endif // ENABLE_ARDUINO_FEATURES == 0 diff --git a/third_party/sdfat/src/common/PrintBasic.h b/third_party/sdfat/src/common/PrintBasic.h new file mode 100644 index 00000000..ac02df55 --- /dev/null +++ b/third_party/sdfat/src/common/PrintBasic.h @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief Stream/Print like replacement for non-Arduino systems. + */ +#include +#include +#include + +#include "../SdFatConfig.h" + +class __FlashStringHelper; + +#ifndef F +#if defined(__AVR__) +#include +#define F(string_literal) \ + (reinterpret_cast(PSTR(string_literal))) +#else // defined(__AVR__) +#define F(str) (str) +#endif // defined(__AVR__) +#endif // F +#ifdef BIN +#undef BIN +#endif // BIN +#define BIN 2 +#define OCT 8 +#define DEC 10 +#define HEX 16 + +class PrintBasic +{ + public: + PrintBasic() : m_error(0) {} + + void clearWriteError() { setWriteError(0); } + int getWriteError() { return m_error; } + virtual void flush() + { /* Empty implementation for backward compatibility */ + } + size_t print(char c) { return write(c); } + size_t print(const char* str) { return write(str); } + size_t print(const __FlashStringHelper* str) + { +#ifdef __AVR__ + PGM_P p = reinterpret_cast(str); + size_t n = 0; + for (uint8_t c; (c = pgm_read_byte(p + n)) && write(c); n++) + { + } + return n; +#else // __AVR__ + return print(reinterpret_cast(str)); +#endif // __AVR__ + } + size_t println(const __FlashStringHelper* str) + { +#ifdef __AVR__ + return print(str) + println(); +#else // __AVR__ + return println(reinterpret_cast(str)); +#endif // __AVR__ + } + size_t print(double n, uint8_t prec = 2) { return printDouble(n, prec); } + size_t print(signed char n, uint8_t base = 10) + { + return print((long)n, base); + } + size_t print(unsigned char n, uint8_t base = 10) + { + return print((unsigned long)n, base); + } + size_t print(int n, uint8_t base = 10) { return print((long)n, base); } + size_t print(unsigned int n, uint8_t base = 10) + { + return print((unsigned long)n, base); + } + size_t print(long n, uint8_t base = 10); + size_t print(unsigned long n, uint8_t base = 10) { return printNum(n, base); } + size_t println() { return write("\r\n"); } + size_t println(char c) { return write(c) + println(); } + size_t println(const char* str) { return print(str) + println(); } + size_t println(double n, uint8_t prec = 2) + { + return print(n, prec) + println(); + } + size_t println(signed char n, uint8_t base = 10) + { + return print(n, base) + println(); + } + size_t println(unsigned char n, uint8_t base = 10) + { + return print(n, base) + println(); + } + size_t println(int n, uint8_t base = 10) + { + return print(n, base) + println(); + } + size_t println(unsigned int n, uint8_t base = 10) + { + return print(n, base) + println(); + } + size_t println(long n, uint8_t base = 10) + { + return print(n, base) + println(); + } + size_t println(unsigned long n, uint8_t base = 10) + { + return print(n, base) + println(); + } + size_t write(const char* str) { return write(str, strlen(str)); } + virtual size_t write(uint8_t b) = 0; + + virtual size_t write(const uint8_t* buffer, size_t size) + { + size_t i; + for (i = 0; i < size; i++) + { + if (!write(buffer[i])) break; + } + return i; + } + size_t write(const char* buffer, size_t size) + { + return write(reinterpret_cast(buffer), size); + } + + protected: + void setWriteError(int err = 1) { m_error = err; } + + private: + size_t printDouble(double n, uint8_t prec); + size_t printNum(unsigned long n, uint8_t base); + int m_error; +}; +//------------------------------------------------------------------------------ +class StreamBasic : public PrintBasic +{ + public: + virtual int available() = 0; + virtual int peek() = 0; + virtual int read() = 0; +}; diff --git a/third_party/sdfat/src/common/SysCall.h b/third_party/sdfat/src/common/SysCall.h new file mode 100644 index 00000000..3631c4b9 --- /dev/null +++ b/third_party/sdfat/src/common/SysCall.h @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief SysCall class + */ +#pragma once +#include +#include + +#include "../SdFatConfig.h" +#if __cplusplus < 201103 +#warning nullptr defined +/** Define nullptr if not C++11 */ +#define nullptr NULL +#endif // __cplusplus < 201103 +//------------------------------------------------------------------------------ +/** Type for FsBlockDevice sector */ +typedef uint32_t Sector_t; +//------------------------------------------------------------------------------ +#if ENABLE_ARDUINO_FEATURES +#if defined(ARDUINO) +/** Use Arduino Print. */ +typedef Print print_t; +/** Use Arduino Stream. */ +typedef Stream stream_t; +#else // defined(ARDUINO) +#error "Unknown system" +#endif // defined(ARDUINO) +//------------------------------------------------------------------------------ +#ifndef F +/** Define macro for strings stored in flash. */ +#define F(str) (str) +#endif // F +//------------------------------------------------------------------------------ +#else // ENABLE_ARDUINO_FEATURES +#include "PrintBasic.h" +/** If not Arduino */ +typedef PrintBasic print_t; +/** If not Arduino */ +class stream_t : public print_t { + public: + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; +}; +#endif // ENABLE_ARDUINO_FEATURES diff --git a/third_party/sdfat/src/common/upcase.cpp b/third_party/sdfat/src/common/upcase.cpp new file mode 100644 index 00000000..c6735ee3 --- /dev/null +++ b/third_party/sdfat/src/common/upcase.cpp @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "upcase.h" + +#include +#ifdef __AVR__ +#include +#define TABLE_MEM PROGMEM +#define readTable8(sym) pgm_read_byte(&sym) +#define readTable16(sym) pgm_read_word(&sym) +#else // __AVR__ +#define TABLE_MEM +#define readTable8(sym) (sym) +#define readTable16(sym) (sym) +#endif // __AVR__ + +struct map16 { + uint16_t base; + int8_t off; + uint8_t count; +}; +typedef struct map16 map16_t; + +struct pair16 { + uint16_t key; + uint16_t val; +}; +typedef struct pair16 pair16_t; +//------------------------------------------------------------------------------ +static const map16_t mapTable[] TABLE_MEM = { + {0X0061, -32, 26}, {0X00E0, -32, 23}, {0X00F8, -32, 7}, {0X0100, 1, 48}, + {0X0132, 1, 6}, {0X0139, 1, 16}, {0X014A, 1, 46}, {0X0179, 1, 6}, + {0X0182, 1, 4}, {0X01A0, 1, 6}, {0X01B3, 1, 4}, {0X01CD, 1, 16}, + {0X01DE, 1, 18}, {0X01F8, 1, 40}, {0X0222, 1, 18}, {0X0246, 1, 10}, + {0X03AD, -37, 3}, {0X03B1, -32, 17}, {0X03C3, -32, 9}, {0X03D8, 1, 24}, + {0X0430, -32, 32}, {0X0450, -80, 16}, {0X0460, 1, 34}, {0X048A, 1, 54}, + {0X04C1, 1, 14}, {0X04D0, 1, 68}, {0X0561, -48, 38}, {0X1E00, 1, 150}, + {0X1EA0, 1, 90}, {0X1F00, 8, 8}, {0X1F10, 8, 6}, {0X1F20, 8, 8}, + {0X1F30, 8, 8}, {0X1F40, 8, 6}, {0X1F60, 8, 8}, {0X1F70, 74, 2}, + {0X1F72, 86, 4}, {0X1F76, 100, 2}, {0X1F7A, 112, 2}, {0X1F7C, 126, 2}, + {0X1F80, 8, 8}, {0X1F90, 8, 8}, {0X1FA0, 8, 8}, {0X1FB0, 8, 2}, + {0X1FD0, 8, 2}, {0X1FE0, 8, 2}, {0X2170, -16, 16}, {0X24D0, -26, 26}, + {0X2C30, -48, 47}, {0X2C67, 1, 6}, {0X2C80, 1, 100}, {0X2D00, 0, 38}, + {0XFF41, -32, 26}, +}; +const size_t MAP_DIM = sizeof(mapTable) / sizeof(map16_t); +//------------------------------------------------------------------------------ +static const pair16_t lookupTable[] TABLE_MEM = { + {0X00FF, 0X0178}, {0X0180, 0X0243}, {0X0188, 0X0187}, {0X018C, 0X018B}, + {0X0192, 0X0191}, {0X0195, 0X01F6}, {0X0199, 0X0198}, {0X019A, 0X023D}, + {0X019E, 0X0220}, {0X01A8, 0X01A7}, {0X01AD, 0X01AC}, {0X01B0, 0X01AF}, + {0X01B9, 0X01B8}, {0X01BD, 0X01BC}, {0X01BF, 0X01F7}, {0X01C6, 0X01C4}, + {0X01C9, 0X01C7}, {0X01CC, 0X01CA}, {0X01DD, 0X018E}, {0X01F3, 0X01F1}, + {0X01F5, 0X01F4}, {0X023A, 0X2C65}, {0X023C, 0X023B}, {0X023E, 0X2C66}, + {0X0242, 0X0241}, {0X0253, 0X0181}, {0X0254, 0X0186}, {0X0256, 0X0189}, + {0X0257, 0X018A}, {0X0259, 0X018F}, {0X025B, 0X0190}, {0X0260, 0X0193}, + {0X0263, 0X0194}, {0X0268, 0X0197}, {0X0269, 0X0196}, {0X026B, 0X2C62}, + {0X026F, 0X019C}, {0X0272, 0X019D}, {0X0275, 0X019F}, {0X027D, 0X2C64}, + {0X0280, 0X01A6}, {0X0283, 0X01A9}, {0X0288, 0X01AE}, {0X0289, 0X0244}, + {0X028A, 0X01B1}, {0X028B, 0X01B2}, {0X028C, 0X0245}, {0X0292, 0X01B7}, + {0X037B, 0X03FD}, {0X037C, 0X03FE}, {0X037D, 0X03FF}, {0X03AC, 0X0386}, + {0X03C2, 0X03A3}, {0X03CC, 0X038C}, {0X03CD, 0X038E}, {0X03CE, 0X038F}, + {0X03F2, 0X03F9}, {0X03F8, 0X03F7}, {0X03FB, 0X03FA}, {0X04CF, 0X04C0}, + {0X1D7D, 0X2C63}, {0X1F51, 0X1F59}, {0X1F53, 0X1F5B}, {0X1F55, 0X1F5D}, + {0X1F57, 0X1F5F}, {0X1F78, 0X1FF8}, {0X1F79, 0X1FF9}, {0X1FB3, 0X1FBC}, + {0X1FCC, 0X1FC3}, {0X1FE5, 0X1FEC}, {0X1FFC, 0X1FF3}, {0X214E, 0X2132}, + {0X2184, 0X2183}, {0X2C61, 0X2C60}, {0X2C76, 0X2C75}, +}; +const size_t LOOKUP_DIM = sizeof(lookupTable) / sizeof(pair16_t); +//------------------------------------------------------------------------------ +static size_t searchPair16(const pair16_t* table, size_t size, uint16_t key) { + size_t left = 0; + size_t right = size; + while (right - left > 1) { + size_t mid = left + (right - left) / 2; + if (readTable16(table[mid].key) <= key) { + left = mid; + } else { + right = mid; + } + } + return left; +} +//------------------------------------------------------------------------------ +uint16_t toUpcase(uint16_t chr) { + uint16_t i, first; + // Optimize for simple ASCII. + if (chr < 127) { + return chr - ('a' <= chr && chr <= 'z' ? 'a' - 'A' : 0); + } + i = searchPair16(reinterpret_cast(mapTable), MAP_DIM, chr); + first = readTable16(mapTable[i].base); + if (first <= chr && (chr - first) < readTable8(mapTable[i].count)) { + int8_t off = readTable8(mapTable[i].off); + if (off == 1) { + return chr - ((chr - first) & 1); + } + return chr + (off ? off : -0x1C60); + } + i = searchPair16(lookupTable, LOOKUP_DIM, chr); + if (readTable16(lookupTable[i].key) == chr) { + return readTable16(lookupTable[i].val); + } + return chr; +} +//------------------------------------------------------------------------------ +uint32_t upcaseChecksum(uint16_t uc, uint32_t sum) { + sum = (sum << 31) + (sum >> 1) + (uc & 0XFF); + sum = (sum << 31) + (sum >> 1) + (uc >> 8); + return sum; +} diff --git a/third_party/sdfat/src/common/upcase.h b/third_party/sdfat/src/common/upcase.h new file mode 100644 index 00000000..b6ceb624 --- /dev/null +++ b/third_party/sdfat/src/common/upcase.h @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include +uint16_t toUpcase(uint16_t chr); +uint32_t upcaseChecksum(uint16_t unicode, uint32_t checksum); diff --git a/third_party/sdfat/src/fmt_src.bat b/third_party/sdfat/src/fmt_src.bat new file mode 100644 index 00000000..0ffffee0 --- /dev/null +++ b/third_party/sdfat/src/fmt_src.bat @@ -0,0 +1,17 @@ +pause format src? +clang-format --style=Google -i *.cpp *.h +rem clang-format --style=Google -i DigitalIO/*.h +rem clang-format --style=Google -i DigitalIO/boards/*.h +clang-format --style=Google -i common/*.cpp common/*.h +clang-format --style=Google -i ExFatLib/*.cpp ExFatLib/*.h +clang-format --style=Google -i FatLib/*.cpp FatLib/*.h +clang-format --style=Google -i FsLib/*.cpp FsLib/*.h +clang-format --style=Google -i iostream/*.cpp iostream/*.h +clang-format --style=Google -i SdCard/*.cpp SdCard/*.h +rem clang-format --style=Google -i SdCard/Rp2040Sdio/DbgLogMsg.h SdCard/Rp2040Sdio/PioDbgInfo.h +rem clang-format --style=Google -i SdCard/Rp2040Sdio/PioSdioCard.h SdCard/Rp2040Sdio/Rp2040SdioConfig.h +clang-format --style=Google -i SdCard/PioSdio/*.cpp SdCard/PioSdio/*.h +clang-format --style=Google -i SdCard/SdSpiCard/*.cpp SdCard/SdSpiCard/*.h +clang-format --style=Google -i SdCard/SdSpiCard/SpiDriver/*.cpp SdCard/SdSpiCard/SpiDriver/*.h +clang-format --style=Google -i SdCard/TeensySdio/*.cpp SdCard/TeensySdio/*.h +pause diff --git a/third_party/sdfat/src/iostream/ArduinoStream.h b/third_party/sdfat/src/iostream/ArduinoStream.h new file mode 100644 index 00000000..df8ffac9 --- /dev/null +++ b/third_party/sdfat/src/iostream/ArduinoStream.h @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief ArduinoInStream and ArduinoOutStream classes + */ +#include "bufstream.h" +//============================================================================== +/** + * \class ArduinoInStream + * \brief Input stream for Arduino Stream objects + */ +class ArduinoInStream : public ibufstream { + public: + /** + * Constructor + * \param[in] hws hardware stream + * \param[in] buf buffer for input line + * \param[in] size size of input buffer + */ + ArduinoInStream(Stream& hws, char* buf, size_t size) { + m_hw = &hws; + m_line = buf; + m_size = size; + } + /** read a line. */ + void readline() { + size_t i = 0; + uint32_t t; + m_line[0] = '\0'; + while (!m_hw->available()) { + yield(); + } + + while (1) { + t = millis(); + while (!m_hw->available()) { + if ((millis() - t) > 10) { + goto done; + } + } + if (i >= (m_size - 1)) { + setstate(failbit); + return; + } + m_line[i++] = m_hw->read(); + m_line[i] = '\0'; + } + done: + init(m_line); + } + + protected: + /** Internal - do not use. + * \param[in] off + * \param[in] way + * \return true/false. + */ + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + /** Internal - do not use. + * \param[in] pos + * \return true/false. + */ + bool seekpos(pos_type pos) { + (void)pos; + return false; + } + + private: + char* m_line; + size_t m_size; + Stream* m_hw; +}; +//============================================================================== +/** + * \class ArduinoOutStream + * \brief Output stream for Arduino Print objects + */ +class ArduinoOutStream : public ostream { + public: + /** constructor + * + * \param[in] pr Print object for this ArduinoOutStream. + */ + explicit ArduinoOutStream(print_t& pr) : m_pr(&pr) {} + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal do not use + * \param[in] c + */ + void putch(char c) { + if (c == '\n') { + m_pr->write('\r'); + } + m_pr->write(c); + } + void putstr(const char* str) { m_pr->write(str); } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + (void)pos; + return false; + } + bool sync() { return true; } + pos_type tellpos() { return 0; } + /// @endcond + private: + ArduinoOutStream() {} + print_t* m_pr; +}; diff --git a/third_party/sdfat/src/iostream/StdioStream.cpp b/third_party/sdfat/src/iostream/StdioStream.cpp new file mode 100644 index 00000000..7b5bc9a6 --- /dev/null +++ b/third_party/sdfat/src/iostream/StdioStream.cpp @@ -0,0 +1,449 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "StdioStream.h" +#ifdef __AVR__ +#include +#endif // __AVR__ +#include "../common/FmtNumber.h" +//------------------------------------------------------------------------------ +int StdioStream::fclose() { + int rtn = 0; + if (!m_status) { + return EOF; + } + if (m_status & S_SWR) { + if (!flushBuf()) { + rtn = EOF; + } + } + if (!StreamBaseFile::close()) { + rtn = EOF; + } + m_r = 0; + m_w = 0; + m_status = 0; + return rtn; +} +//------------------------------------------------------------------------------ +int StdioStream::fflush() { + if ((m_status & (S_SWR | S_SRW)) && !(m_status & S_SRD)) { + if (flushBuf() && StreamBaseFile::sync()) { + return 0; + } + } + return EOF; +} +//------------------------------------------------------------------------------ +char* StdioStream::fgets(char* str, size_t num, size_t* len) { + char* s = str; + if (num-- == 0) { + return 0; + } + while (num) { + size_t n; + if ((n = m_r) == 0) { + if (!fillBuf()) { + if (s == str) { + return 0; + } + break; + } + n = m_r; + } + if (n > num) { + n = num; + } + uint8_t* end = reinterpret_cast(memchr(m_p, '\n', n)); + if (end != 0) { + n = ++end - m_p; + memcpy(s, m_p, n); + m_r -= n; + m_p = end; + s += n; + break; + } + memcpy(s, m_p, n); + m_r -= n; + m_p += n; + s += n; + num -= n; + } + *s = 0; + if (len) { + *len = s - str; + } + return str; +} +//------------------------------------------------------------------------------ +bool StdioStream::fopen(const char* path, const char* mode) { + oflag_t oflag; + uint8_t m; + switch (*mode++) { + case 'a': + m = O_WRONLY; + oflag = O_CREAT | O_APPEND; + m_status = S_SWR; + break; + + case 'r': + m = O_RDONLY; + oflag = 0; + m_status = S_SRD; + break; + + case 'w': + m = O_WRONLY; + oflag = O_CREAT | O_TRUNC; + m_status = S_SWR; + break; + + default: + goto fail; + } + while (*mode) { + switch (*mode++) { + case '+': + m_status = S_SRW; + m = O_RDWR; + break; + + case 'b': + break; + + case 'x': + oflag |= O_EXCL; + break; + + default: + goto fail; + } + } + oflag |= m; + if (!StreamBaseFile::open(path, oflag)) { + goto fail; + } + m_r = 0; + m_w = 0; + m_p = m_buf; + return true; + +fail: + m_status = 0; + return false; +} +//------------------------------------------------------------------------------ +int StdioStream::fputs(const char* str) { + size_t len = strlen(str); + return fwrite(str, 1, len) == len ? len : EOF; +} +//------------------------------------------------------------------------------ +size_t StdioStream::fread(void* ptr, size_t size, size_t count) { + uint8_t* dst = reinterpret_cast(ptr); + size_t total = size * count; + if (total == 0) { + return 0; + } + size_t need = total; + while (need > m_r) { + memcpy(dst, m_p, m_r); + dst += m_r; + m_p += m_r; + need -= m_r; + if (!fillBuf()) { + return (total - need) / size; + } + } + memcpy(dst, m_p, need); + m_r -= need; + m_p += need; + return count; +} +//------------------------------------------------------------------------------ +int StdioStream::fseek(int32_t offset, int origin) { + int32_t pos; + if (m_status & S_SWR) { + if (!flushBuf()) { + goto fail; + } + } + switch (origin) { + case SEEK_CUR: + pos = ftell(); + if (pos < 0) { + goto fail; + } + pos += offset; + if (!StreamBaseFile::seekCur(pos)) { + goto fail; + } + break; + + case SEEK_SET: + if (offset < 0) { + goto fail; + } + if (!StreamBaseFile::seekSet(static_cast(offset))) { + goto fail; + } + break; + + case SEEK_END: + if (!StreamBaseFile::seekEnd(offset)) { + goto fail; + } + break; + + default: + goto fail; + } + m_r = 0; + m_p = m_buf; + return 0; + +fail: + return EOF; +} +//------------------------------------------------------------------------------ +int32_t StdioStream::ftell() { + uint32_t pos = StreamBaseFile::curPosition(); + if (m_status & S_SRD) { + if (m_r > pos) { + return -1L; + } + pos -= m_r; + } else if (m_status & S_SWR) { + pos += m_p - m_buf; + } + return pos; +} +//------------------------------------------------------------------------------ +size_t StdioStream::fwrite(const void* ptr, size_t size, size_t count) { + return write(ptr, count * size) < 0 ? EOF : count; +} +//------------------------------------------------------------------------------ +// allow shadow of rewind() in StreamBaseFile, +// cppcheck-suppress duplInheritedMember +int StdioStream::write(const void* buf, size_t count) { + const uint8_t* src = static_cast(buf); + size_t todo = count; + + while (todo > m_w) { + memcpy(m_p, src, m_w); + m_p += m_w; + src += m_w; + todo -= m_w; + if (!flushBuf()) { + return EOF; + } + } + memcpy(m_p, src, todo); + m_p += todo; + m_w -= todo; + return count; +} +//------------------------------------------------------------------------------ +#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) +size_t StdioStream::print(const __FlashStringHelper* str) { +#ifdef __AVR__ + PGM_P p = reinterpret_cast(str); + uint8_t c; + while ((c = pgm_read_byte(p))) { + if (putc(c) < 0) { + return 0; + } + p++; + } + return p - reinterpret_cast(str); +#else // __AVR__ + return print(reinterpret_cast(str)); +#endif // __AVR__ +} +#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) +//------------------------------------------------------------------------------ +int StdioStream::printDec(float value, uint8_t prec) { + char buf[24]; + const char* ptr = fmtDouble(buf + sizeof(buf), value, prec, false); + return write(ptr, buf + sizeof(buf) - ptr); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(signed char n) { + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + } + return printDec((unsigned char)n); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(int16_t n) { + int s; + uint8_t rtn = 0; + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + rtn++; + } + if ((s = printDec(static_cast(n))) < 0) { + return s; + } + return rtn; +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(uint16_t n) { + char buf[5]; + const char* ptr = fmtBase10(buf + sizeof(buf), n); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(int32_t n) { + uint8_t s = 0; + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + s = 1; + } + int rtn = printDec(static_cast(n)); + return rtn > 0 ? rtn + s : -1; +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(uint32_t n) { + char buf[10]; + const char* ptr = fmtBase10(buf + sizeof(buf), n); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +} +//------------------------------------------------------------------------------ +int StdioStream::printHex(uint32_t n) { + char buf[8]; + const char* ptr = fmtHex(buf + sizeof(buf), n); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +} +//------------------------------------------------------------------------------ +// allow shadow of rewind() in StreamBaseFile, +// cppcheck-suppress duplInheritedMember +bool StdioStream::rewind() { + if (m_status & S_SWR) { + if (!flushBuf()) { + return false; + } + } + StreamBaseFile::seekSet(0UL); + m_r = 0; + return true; +} +//------------------------------------------------------------------------------ +int StdioStream::ungetc(int c) { + // error if EOF. + if (c == EOF) { + return EOF; + } + // error if not reading. + if ((m_status & S_SRD) == 0) { + return EOF; + } + // error if no space. + if (m_p == m_buf) { + return EOF; + } + m_r++; + m_status &= ~S_EOF; + return *--m_p = (uint8_t)c; +} +//============================================================================== +// private +//------------------------------------------------------------------------------ +int StdioStream::fillGet() { + if (!fillBuf()) { + return EOF; + } + m_r--; + return *m_p++; +} +//------------------------------------------------------------------------------ +// private +bool StdioStream::fillBuf() { + if (!(m_status & S_SRD)) { // check for S_ERR and S_EOF ??///////////////// + if (!(m_status & S_SRW)) { + m_status |= S_ERR; + return false; + } + if (m_status & S_SWR) { + if (!flushBuf()) { + return false; + } + m_status &= ~S_SWR; + m_status |= S_SRD; + m_w = 0; + } + } + m_p = m_buf + UNGETC_BUF_SIZE; + int nr = StreamBaseFile::read(m_p, sizeof(m_buf) - UNGETC_BUF_SIZE); + if (nr <= 0) { + m_status |= nr < 0 ? S_ERR : S_EOF; + m_r = 0; + return false; + } + m_r = nr; + return true; +} +//------------------------------------------------------------------------------ +// private +bool StdioStream::flushBuf() { + if (!(m_status & S_SWR)) { + if (!(m_status & S_SRW)) { + m_status |= S_ERR; + return false; + } + m_status &= ~S_SRD; + m_status |= S_SWR; + m_r = 0; + m_w = sizeof(m_buf); + m_p = m_buf; + return true; + } + uint8_t n = m_p - m_buf; + m_p = m_buf; + m_w = sizeof(m_buf); + if (StreamBaseFile::write(m_buf, n) == n) { + return true; + } + m_status |= S_ERR; + return false; +} +//------------------------------------------------------------------------------ +int StdioStream::flushPut(uint8_t c) { + if (!flushBuf()) { + return EOF; + } + m_w--; + return *m_p++ = c; +} diff --git a/third_party/sdfat/src/iostream/StdioStream.h b/third_party/sdfat/src/iostream/StdioStream.h new file mode 100644 index 00000000..ab9a66f8 --- /dev/null +++ b/third_party/sdfat/src/iostream/StdioStream.h @@ -0,0 +1,610 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief StdioStream class + */ +#include + +#include "ios.h" +//------------------------------------------------------------------------------ +/** Total size of stream buffer. The entire buffer is used for output. + * During input UNGETC_BUF_SIZE of this space is reserved for ungetc. + */ +const uint8_t STREAM_BUF_SIZE = 64; +/** Amount of buffer allocated for ungetc during input. */ +const uint8_t UNGETC_BUF_SIZE = 2; +//------------------------------------------------------------------------------ +// Get rid of any macros defined in . +#include +#undef clearerr +#undef fclose +#undef feof +#undef ferror +#undef fflush +#undef fgetc +#undef fgetpos +#undef fgets +#undef fopen +#undef fprintf +#undef fputc +#undef fputs +#undef fread +#undef freopen +#undef fscanf +#undef fseek +#undef fsetpos +#undef ftell +#undef fwrite +#undef getc +#undef getchar +#undef gets +#undef perror +// #undef printf // NOLINT +#undef putc +#undef putchar +#undef puts +#undef remove +#undef rename +#undef rewind +#undef scanf +#undef setbuf +#undef setvbuf +// #undef sprintf // NOLINT +#undef sscanf +#undef tmpfile +#undef tmpnam +#undef ungetc +#undef vfprintf +#undef vprintf +#undef vsprintf + +// make sure needed macros are defined +#ifndef EOF +/** End-of-file return value. */ +#define EOF (-1) +#endif // EOF +#ifndef NULL +/** Null pointer */ +#define NULL 0 +#endif // NULL +#ifndef SEEK_CUR +/** Seek relative to current position. */ +#define SEEK_CUR 1 +#endif // SEEK_CUR +#ifndef SEEK_END +/** Seek relative to end-of-file. */ +#define SEEK_END 2 +#endif // SEEK_END +#ifndef SEEK_SET +/** Seek relative to start-of-file. */ +#define SEEK_SET 0 +#endif // SEEK_SET +//------------------------------------------------------------------------------ +/** \class StdioStream + * \brief StdioStream implements a minimal stdio stream. + * + * StdioStream does not support subdirectories or long file names. + */ +class StdioStream : private StreamBaseFile { + public: + using StreamBaseFile::printField; + /** Constructor + * + */ + StdioStream() : m_buf{0} {} + //---------------------------------------------------------------------------- + /** Clear the stream's end-of-file and error indicators. */ + void clearerr() { m_status &= ~(S_ERR | S_EOF); } + //---------------------------------------------------------------------------- + /** Close a stream. + * + * A successful call to the fclose function causes the stream to be + * flushed and the associated file to be closed. Any unwritten buffered + * data is written to the file; any unread buffered data is discarded. + * Whether or not the call succeeds, the stream is disassociated from + * the file. + * + * \return zero if the stream was successfully closed, or EOF if any any + * errors are detected. + */ + int fclose(); + //---------------------------------------------------------------------------- + /** Test the stream's end-of-file indicator. + * \return non-zero if and only if the end-of-file indicator is set. + */ + int feof() { return (m_status & S_EOF) != 0; } + //---------------------------------------------------------------------------- + /** Test the stream's error indicator. + * \return return non-zero if and only if the error indicator is set. + */ + int ferror() { return (m_status & S_ERR) != 0; } + //---------------------------------------------------------------------------- + /** Flush the stream. + * + * If stream is an output stream or an update stream in which the most + * recent operation was not input, any unwritten data is written to the + * file; otherwise the call is an error since any buffered input data + * would be lost. + * + * \return sets the error indicator for the stream and returns EOF if an + * error occurs, otherwise it returns zero. + */ + int fflush(); + //---------------------------------------------------------------------------- + /** Get a byte from the stream. + * + * \return If the end-of-file indicator for the stream is set, or if the + * stream is at end-of-file, the end-of-file indicator for the stream is + * set and the fgetc function returns EOF. Otherwise, the fgetc function + * returns the next character from the input stream. + */ + int fgetc() { return m_r-- == 0 ? fillGet() : *m_p++; } + //---------------------------------------------------------------------------- + /** Get a string from a stream. + * + * The fgets function reads at most one less than the number of + * characters specified by num from the stream into the array pointed + * to by str. No additional characters are read after a new-line + * character (which is retained) or after end-of-file. A null character + * is written immediately after the last character read into the array. + * + * \param[out] str Pointer to an array of where the string is copied. + * + * \param[in] num Maximum number of characters including the null + * character. + * + * \param[out] len If len is not null and fgets is successful, the + * length of the string is returned. + * + * \return str if successful. If end-of-file is encountered and no + * characters have been read into the array, the contents of the array + * remain unchanged and a null pointer is returned. If a read error + * occurs during the operation, the array contents are indeterminate + * and a null pointer is returned. + */ + char* fgets(char* str, size_t num, size_t* len = 0); + //---------------------------------------------------------------------------- + /** Open a stream. + * + * Open a file and associates the stream with it. + * + * \param[in] path file to be opened. + * + * \param[in] mode a string that indicates the open mode. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
"r" or "rb"Open a file for reading. The file must exist.
"w" or "wb"Truncate an existing to zero length or create an empty file + * for writing.
"wx" or "wbx"Create a file for writing. Fails if the file already exists.
"a" or "ab"Append; open or create file for writing at end-of-file.
"r+" or "rb+" or "r+b"Open a file for update (reading and writing).
"w+" or "w+b" or "wb+"Truncate an existing to zero length or create a file for update.
"w+x" or "w+bx" or "wb+x"Create a file for update. Fails if the file already exists.
"a+" or "a+b" or "ab+"Append; open or create a file for update, writing at end-of-file.
+ * The character 'b' shall have no effect, but is allowed for ISO C + * standard conformance. + * + * Opening a file with append mode causes all subsequent writes to the + * file to be forced to the then current end-of-file, regardless of + * intervening calls to the fseek function. + * + * When a file is opened with update mode, both input and output may be + * performed on the associated stream. However, output shall not be + * directly followed by input without an intervening call to the fflush + * function or to a file positioning function (fseek, or rewind), and + * input shall not be directly followed by output without an intervening + * call to a file positioning function, unless the input operation + * encounters end-of-file. + * + * \return true for success or false for failure. + */ + bool fopen(const char* path, const char* mode); + //---------------------------------------------------------------------------- + /** Write a byte to a stream. + * + * \param[in] c the byte to be written (converted to an unsigned char). + * + * \return Upon successful completion, fputc() returns the value it + * has written. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + int fputc(int c) { return m_w-- == 0 ? flushPut(c) : *m_p++ = c; } + //---------------------------------------------------------------------------- + /** Write a string to a stream. + * + * \param[in] str a pointer to the string to be written. + * + * \return for success, fputs() returns a non-negative + * number. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + int fputs(const char* str); + //---------------------------------------------------------------------------- + /** Binary input. + * + * Reads an array of up to count elements, each one with a size of size + * bytes. + * \param[out] ptr pointer to area of at least (size*count) bytes where + * the data will be stored. + * + * \param[in] size the size, in bytes, of each element to be read. + * + * \param[in] count the number of elements to be read. + * + * \return number of elements successfully read, which may be less than + * count if a read error or end-of-file is encountered. If size or count + * is zero, fread returns zero and the contents of the array and the + * state of the stream remain unchanged. + */ + size_t fread(void* ptr, size_t size, size_t count); + //---------------------------------------------------------------------------- + /** Set the file position for the stream. + * + * \param[in] offset number of offset from the origin. + * + * \param[in] origin position used as reference for the offset. It is + * specified by one of the following constants. + * + * SEEK_SET - Beginning of file. + * + * SEEK_CUR - Current position of the file pointer. + * + * SEEK_END - End of file. + * + * \return zero for success. Otherwise, it returns non-zero and sets the + * error indicator for the stream. + */ + int fseek(int32_t offset, int origin); + //---------------------------------------------------------------------------- + /** Get the current position in a stream. + * + * \return If successful, ftell return the current value of the position + * indicator. On failure, ftell returns −1L. + */ + int32_t ftell(); + //---------------------------------------------------------------------------- + /** Binary output. + * + * Writes an array of up to count elements, each one with a size of size + * bytes. + * \param[in] ptr pointer to (size*count) bytes of data to be written. + * + * \param[in] size the size, in bytes, of each element to be written. + * + * \param[in] count the number of elements to be written. + * + * \return number of elements successfully written. if this number is + * less than count, an error has occurred. If size or count is zero, + * fwrite returns zero. + */ + size_t fwrite(const void* ptr, size_t size, size_t count); + //---------------------------------------------------------------------------- + /** Get a byte from the stream. + * + * getc and fgetc are equivalent but getc is in-line so it is faster but + * require more flash memory. + * + * \return If the end-of-file indicator for the stream is set, or if the + * stream is at end-of-file, the end-of-file indicator for the stream is + * set and the fgetc function returns EOF. Otherwise, the fgetc function + * returns the next character from the input stream. + */ + inline __attribute__((always_inline)) int getc() { + return m_r-- == 0 ? fillGet() : *m_p++; + } + //---------------------------------------------------------------------------- + /** Write a byte to a stream. + * + * putc and fputc are equivalent but putc is in-line so it is faster but + * require more flash memory. + * + * \param[in] c the byte to be written (converted to an unsigned char). + * + * \return Upon successful completion, fputc() returns the value it + * has written. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + inline __attribute__((always_inline)) int putc(int c) { + return m_w-- == 0 ? flushPut(c) : *m_p++ = c; + } + //---------------------------------------------------------------------------- + /** Write a CR/LF. + * + * \return two, the number of bytes written, for success or -1 for failure. + */ + inline __attribute__((always_inline)) int putCRLF() { + if (m_w < 2) { + if (!flushBuf()) { + return -1; + } + } + *m_p++ = '\r'; + *m_p++ = '\n'; + m_w -= 2; + return 2; + } + //---------------------------------------------------------------------------- + /** Write a character. + * \param[in] c the character to write. + * \return the number of bytes written. + */ + size_t print(char c) { return putc(c) < 0 ? 0 : 1; } + //---------------------------------------------------------------------------- + /** Write a string. + * + * \param[in] str the string to be written. + * + * \return the number of bytes written. + */ + size_t print(const char* str) { + int n = fputs(str); + return n < 0 ? 0 : n; + } + //---------------------------------------------------------------------------- +#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + /** Print a string stored in flash memory. + * + * \param[in] str the string to print. + * + * \return the number of bytes written. + */ + size_t print(const __FlashStringHelper* str); +#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + //---------------------------------------------------------------------------- + /** Print a floating point number. + * + * \param[in] prec Number of digits after decimal point. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + size_t print(double val, uint8_t prec = 2) { + return print(static_cast(val), prec); + } + //---------------------------------------------------------------------------- + /** Print a floating point number. + * + * \param[in] prec Number of digits after decimal point. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + size_t print(float val, uint8_t prec = 2) { + int n = printDec(val, prec); + return n > 0 ? n : 0; + } + //---------------------------------------------------------------------------- + /** Print a number. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + template + size_t print(T val) { + int n = printDec(val); + return n > 0 ? n : 0; + } + //---------------------------------------------------------------------------- + /** Write a CR/LF. + * + * \return two, the number of bytes written, for success or zero for failure. + */ + size_t println() { return putCRLF() > 0 ? 2 : 0; } + //---------------------------------------------------------------------------- + /** Print a floating point number followed by CR/LF. + * + * \param[in] val the number to be printed. + * + * \param[in] prec Number of digits after decimal point. + * + * \return the number of bytes written. + */ + size_t println(double val, uint8_t prec = 2) { + return println(static_cast(val), prec); + } + //---------------------------------------------------------------------------- + /** Print a floating point number followed by CR/LF. + * + * \param[in] val the number to be printed. + * + * \param[in] prec Number of digits after decimal point. + * + * \return the number of bytes written. + */ + size_t println(float val, uint8_t prec = 2) { + int n = printDec(val, prec); + return n > 0 && putCRLF() > 0 ? n + 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print an item followed by CR/LF + * + * \param[in] val the item to be printed. + * + * \return the number of bytes written. + */ + template + size_t println(T val) { + int n = print(val); + return putCRLF() > 0 ? n + 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print a char as a number. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(char n) { + if (CHAR_MIN == 0) { + return printDec((unsigned char)n); + } else { + return printDec((signed char)n); + } + } + //---------------------------------------------------------------------------- + /** print a signed 8-bit integer + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(signed char n); + //---------------------------------------------------------------------------- + /** Print an unsigned 8-bit number. + * \param[in] n number to be print. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(unsigned char n) { return printDec(static_cast(n)); } + //---------------------------------------------------------------------------- + /** Print a int16_t + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(int16_t n); + //---------------------------------------------------------------------------- + /** print a uint16_t. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(uint16_t n); + //---------------------------------------------------------------------------- + /** Print a signed 32-bit integer. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(int32_t n); + //---------------------------------------------------------------------------- + /** Write an unsigned 32-bit number. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(uint32_t n); + //---------------------------------------------------------------------------- + /** Print a double. + * \param[in] value The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(double value, uint8_t prec) { + return printDec(static_cast(value), prec); + } + //---------------------------------------------------------------------------- + /** Print a float. + * \param[in] value The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(float value, uint8_t prec); + //---------------------------------------------------------------------------- + /** Print HEX + * \param[in] n number to be printed as HEX. + * + * \return The number of bytes written or -1 if an error occurs. + */ + int printHex(uint32_t n); + //---------------------------------------------------------------------------- + /** Print HEX with CRLF + * \param[in] n number to be printed as HEX. + * + * \return The number of bytes written or -1 if an error occurs. + */ + int printHexln(uint32_t n) { + int rtn = printHex(n); + return rtn < 0 || putCRLF() != 2 ? -1 : rtn + 2; + } + //---------------------------------------------------------------------------- + /** Set position of a stream to the beginning. + * + * The rewind function sets the file position to the beginning of the + * file. It is equivalent to fseek(0L, SEEK_SET) except that the error + * indicator for the stream is also cleared. + * + * \return true for success or false for failure. + */ + bool rewind(); + //---------------------------------------------------------------------------- + /** Push a byte back into an input stream. + * + * \param[in] c the byte (converted to an unsigned char) to be pushed back. + * + * One character of push-back is guaranteed. If the ungetc function is + * called too many times without an intervening read or file positioning + * operation on that stream, the operation may fail. + * + * A successful intervening call to a file positioning function (fseek, + * fsetpos, or rewind) discards any pushed-back characters for the stream. + * + * \return Upon successful completion, ungetc() returns the byte pushed + * back after conversion. Otherwise it returns EOF. + */ + int ungetc(int c); + //============================================================================ + private: + bool fillBuf(); + int fillGet(); + bool flushBuf(); + int flushPut(uint8_t c); + int write(const void* buf, size_t count); + //---------------------------------------------------------------------------- + // S_SRD and S_WR are never simultaneously asserted + static const uint8_t S_SRD = 0x01; // OK to read + static const uint8_t S_SWR = 0x02; // OK to write + static const uint8_t S_SRW = 0x04; // open for reading & writing + static const uint8_t S_EOF = 0x10; // found EOF + static const uint8_t S_ERR = 0x20; // found error + //---------------------------------------------------------------------------- + uint8_t m_buf[STREAM_BUF_SIZE]; + uint8_t m_status = 0; + uint8_t* m_p = m_buf; + uint8_t m_r = 0; + uint8_t m_w = 0; +}; diff --git a/third_party/sdfat/src/iostream/StreamBaseClass.cpp b/third_party/sdfat/src/iostream/StreamBaseClass.cpp new file mode 100644 index 00000000..432402f7 --- /dev/null +++ b/third_party/sdfat/src/iostream/StreamBaseClass.cpp @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "fstream.h" +//------------------------------------------------------------------------------ +int16_t StreamBaseClass::getch() { + uint8_t c; + int8_t s = StreamBaseFile::read(&c, 1); + if (s != 1) { + if (s < 0) { + setstate(badbit); + } else { + setstate(eofbit); + } + return -1; + } + if (c != '\r' || (getmode() & ios::binary)) { + return c; + } + s = StreamBaseFile::read(&c, 1); + if (s == 1 && c == '\n') { + return c; + } + if (s == 1) { + StreamBaseFile::seekCur(-1); + } + return '\r'; +} +//------------------------------------------------------------------------------ +void StreamBaseClass::open(const char* path, ios::openmode mode) { + oflag_t oflag; + clearWriteError(); + switch (mode & (app | in | out | trunc)) { + case app | in: + case app | in | out: + oflag = O_RDWR | O_APPEND | O_CREAT; + break; + + case app: + case app | out: + oflag = O_WRONLY | O_APPEND | O_CREAT; + break; + + case in: + oflag = O_RDONLY; + break; + + case in | out: + oflag = O_RDWR; + break; + + case in | out | trunc: + oflag = O_RDWR | O_TRUNC | O_CREAT; + break; + + case out: + case out | trunc: + oflag = O_WRONLY | O_TRUNC | O_CREAT; + break; + + default: + goto fail; + } + if (mode & ios::ate) { + oflag |= O_AT_END; + } + if (!StreamBaseFile::open(path, oflag)) { + goto fail; + } + setmode(mode); + clear(); + return; + +fail: + StreamBaseFile::close(); + setstate(failbit); + return; +} +//------------------------------------------------------------------------------ +void StreamBaseClass::putch(char c) { + if (c == '\n' && !(getmode() & ios::binary)) { + write('\r'); + } + write(c); + if (getWriteError()) { + setstate(badbit); + } +} +//------------------------------------------------------------------------------ +void StreamBaseClass::putstr(const char* str) { + size_t n = 0; + while (1) { + char c = str[n]; + if (c == '\0' || (c == '\n' && !(getmode() & ios::binary))) { + if (n > 0) { + write(str, n); + } + if (c == '\0') { + break; + } + write('\r'); + str += n; + n = 0; + } + n++; + } + if (getWriteError()) { + setstate(badbit); + } +} +//------------------------------------------------------------------------------ +bool StreamBaseClass::seekoff(off_type off, seekdir way) { + pos_type pos; + switch (way) { + case beg: + pos = off; + break; + + case cur: + pos = StreamBaseFile::curPosition() + off; + break; + + case end: + pos = StreamBaseFile::fileSize() + off; + break; + + default: + return false; + } + return seekpos(pos); +} diff --git a/third_party/sdfat/src/iostream/bufstream.h b/third_party/sdfat/src/iostream/bufstream.h new file mode 100644 index 00000000..185592e3 --- /dev/null +++ b/third_party/sdfat/src/iostream/bufstream.h @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief \ref ibufstream and \ref obufstream classes + */ +#include + +#include "iostream.h" +//============================================================================== +/** + * \class ibufstream + * \brief parse a char string + */ +class ibufstream : public istream { + public: + /** Constructor */ + ibufstream() {} + /** Constructor + * \param[in] str pointer to string to be parsed + * Warning: The string will not be copied so must stay in scope. + */ + explicit ibufstream(const char* str) { init(str); } + /** Initialize an ibufstream + * \param[in] str pointer to string to be parsed + * Warning: The string will not be copied so must stay in scope. + */ + void init(const char* str) { + m_buf = str; + m_len = strlen(m_buf); + m_pos = 0; + clear(); + } + + protected: + /// @cond SHOW_PROTECTED + int16_t getch() { + if (m_pos < m_len) { + return m_buf[m_pos++]; + } + setstate(eofbit); + return -1; + } + void getpos(pos_t* pos) { pos->position = m_pos; } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + if (pos < m_len) { + m_pos = pos; + return true; + } + return false; + } + void setpos(const pos_t* pos) { m_pos = pos->position; } + pos_type tellpos() { return m_pos; } + /// @endcond + private: + const char* m_buf = nullptr; + size_t m_len = 0; + size_t m_pos; +}; +//============================================================================== +/** + * \class obufstream + * \brief format a char string + */ +class obufstream : public ostream { + public: + /** constructor */ + obufstream() {} + /** Constructor + * \param[in] buf buffer for formatted string + * \param[in] size buffer size + */ + obufstream(char* buf, size_t size) { init(buf, size); } + /** Initialize an obufstream + * \param[in] buf buffer for formatted string + * \param[in] size buffer size + */ + void init(char* buf, size_t size) { + m_buf = buf; + buf[0] = '\0'; + m_size = size; + m_in = 0; + } + /** \return a pointer to the buffer */ + char* buf() { return m_buf; } + /** \return the length of the formatted string */ + size_t length() { return m_in; } + + protected: + /// @cond SHOW_PROTECTED + void putch(char c) { + if ((m_in + 1) >= m_size) { + setstate(badbit); + return; + } + m_buf[m_in++] = c; + m_buf[m_in] = '\0'; + } + void putstr(const char* str) { + while (*str) { + putch(*str++); + } + } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + if (pos > m_in) { + return false; + } + m_in = pos; + m_buf[m_in] = '\0'; + return true; + } + bool sync() { return true; } + pos_type tellpos() { return m_in; } + /// @endcond + private: + char* m_buf = nullptr; + size_t m_size = 0; + size_t m_in = 0; +}; diff --git a/third_party/sdfat/src/iostream/fstream.h b/third_party/sdfat/src/iostream/fstream.h new file mode 100644 index 00000000..d1a032cb --- /dev/null +++ b/third_party/sdfat/src/iostream/fstream.h @@ -0,0 +1,258 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief iostreams for files. + */ +#pragma once +#include "iostream.h" +//------------------------------------------------------------------------------ +/** + * \class StreamBaseClass + * \brief base type for FAT and exFAT streams + */ +class StreamBaseClass : protected StreamBaseFile, virtual public ios { + protected: + using StreamBaseFile::clearWriteError; + using StreamBaseFile::getWriteError; + using StreamBaseFile::write; + + /* Internal do not use + * \return mode + */ + int16_t getch(); + + void open(const char* path, ios::openmode mode); + /** Internal do not use + * \return mode + */ + ios::openmode getmode() { return m_mode; } + void putch(char c); + void putstr(const char* str); + bool seekoff(off_type off, seekdir way); + /** Internal do not use + * \param[in] pos + */ + bool seekpos(pos_type pos) { return StreamBaseFile::seekSet(pos); } + /** Internal do not use + * \param[in] mode + */ + void setmode(ios::openmode mode) { m_mode = mode; } + + private: + ios::openmode m_mode; +}; +//============================================================================== +/** + * \class fstream + * \brief file input/output stream. + */ +class fstream : public iostream, StreamBaseClass { + public: + using iostream::peek; + using StreamBaseClass::close; + fstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit fstream(const char* path, openmode mode = in | out) { + open(path, mode); + } +#if DESTRUCTOR_CLOSES_FILE + ~fstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Clear state and writeError + * \param[in] state new state for stream + */ + void clear(iostate state = goodbit) override { + ios::clear(state); + StreamBaseClass::clearWriteError(); + } + /** Open a fstream + * \param[in] path path to open + * \param[in] mode open mode + * + * Valid open modes are (at end, ios::ate, and/or ios::binary may be added): + * + * ios::in - Open file for reading. + * + * ios::out or ios::out | ios::trunc - Truncate to 0 length, if existent, + * or create a file for writing only. + * + * ios::app or ios::out | ios::app - Append; open or create file for + * writing at end-of-file. + * + * ios::in | ios::out - Open file for update (reading and writing). + * + * ios::in | ios::out | ios::trunc - Truncate to zero length, if existent, + * or create file for update. + * + * ios::in | ios::app or ios::in | ios::out | ios::app - Append; open or + * create text file for update, writing at end of file. + */ + void open(const char* path, openmode mode = in | out) { + StreamBaseClass::open(path, mode); + } + /** \return True if stream is open else false. */ + bool is_open() { return StreamBaseFile::isOpen(); } + + protected: + /// @cond SHOW_PROTECTED + /** Internal - do not use + * \return + */ + int16_t getch() override { return StreamBaseClass::getch(); } + /** Internal - do not use + * \param[out] pos + */ + void getpos(pos_t* pos) override { StreamBaseFile::fgetpos(pos); } + /** Internal - do not use + * \param[in] c + */ + void putch(char c) override { StreamBaseClass::putch(c); } + /** Internal - do not use + * \param[in] str + */ + void putstr(const char* str) override { StreamBaseClass::putstr(str); } + /** Internal - do not use + * \param[in] pos + */ + bool seekoff(off_type off, seekdir way) override { + return StreamBaseClass::seekoff(off, way); + } + bool seekpos(pos_type pos) override { return StreamBaseClass::seekpos(pos); } + void setpos(const pos_t* pos) override { StreamBaseFile::fsetpos(pos); } + bool sync() override { return StreamBaseClass::sync(); } + pos_type tellpos() override { return StreamBaseFile::curPosition(); } + /// @endcond +}; +//============================================================================== +/** + * \class ifstream + * \brief file input stream. + */ +class ifstream : public istream, StreamBaseClass { + public: + using istream::peek; + using StreamBaseClass::close; + ifstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit ifstream(const char* path, openmode mode = in) { open(path, mode); } +#if DESTRUCTOR_CLOSES_FILE + ~ifstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** \return True if stream is open else false. */ + bool is_open() { return StreamBaseFile::isOpen(); } + /** Open an ifstream + * \param[in] path file to open + * \param[in] mode open mode + * + * \a mode See fstream::open() for valid modes. + */ + void open(const char* path, openmode mode = in) { + StreamBaseClass::open(path, mode | in); + } + + protected: + /// @cond SHOW_PROTECTED + /** Internal - do not use + * \return + */ + int16_t getch() override { return StreamBaseClass::getch(); } + /** Internal - do not use + * \param[out] pos + */ + void getpos(pos_t* pos) override { StreamBaseFile::fgetpos(pos); } + /** Internal - do not use + * \param[in] pos + */ + bool seekoff(off_type off, seekdir way) override { + return StreamBaseClass::seekoff(off, way); + } + bool seekpos(pos_type pos) override { return StreamBaseClass::seekpos(pos); } + void setpos(const pos_t* pos) override { StreamBaseFile::fsetpos(pos); } + pos_type tellpos() override { return StreamBaseFile::curPosition(); } + /// @endcond +}; +//============================================================================== +/** + * \class ofstream + * \brief file output stream. + */ +class ofstream : public ostream, StreamBaseClass { + public: + using StreamBaseClass::close; + ofstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit ofstream(const char* path, openmode mode = out) { open(path, mode); } +#if DESTRUCTOR_CLOSES_FILE + ~ofstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Clear state and writeError + * \param[in] state new state for stream + */ + void clear(iostate state = goodbit) override { + ios::clear(state); + StreamBaseClass::clearWriteError(); + } + /** Open an ofstream + * \param[in] path file to open + * \param[in] mode open mode + * + * \a mode See fstream::open() for valid modes. + */ + void open(const char* path, openmode mode = out) { + StreamBaseClass::open(path, mode | out); + } + /** \return True if stream is open else false. */ + bool is_open() { return StreamBaseFile::isOpen(); } + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal do not use + * \param[in] c + */ + void putch(char c) override { StreamBaseClass::putch(c); } + void putstr(const char* str) override { StreamBaseClass::putstr(str); } + bool seekoff(off_type off, seekdir way) override { + return StreamBaseClass::seekoff(off, way); + } + bool seekpos(pos_type pos) override { return StreamBaseClass::seekpos(pos); } + /** + * Internal do not use + * \param[in] b + */ + bool sync() override { return StreamBaseClass::sync(); } + pos_type tellpos() override { return StreamBaseFile::curPosition(); } + /// @endcond +}; diff --git a/third_party/sdfat/src/iostream/ios.h b/third_party/sdfat/src/iostream/ios.h new file mode 100644 index 00000000..5e2759d3 --- /dev/null +++ b/third_party/sdfat/src/iostream/ios.h @@ -0,0 +1,423 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "../FsLib/FsLib.h" +/** + * \file + * \brief \ref ios_base and \ref ios classes + */ +//============================================================================== +/** For internal use in c++ streams */ +typedef fspos_t pos_t; +//============================================================================== +#if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN) +/** Set File type for iostreams. */ +typedef FatFile StreamBaseFile; +#elif SDFAT_FILE_TYPE == 2 +typedef ExFatFile StreamBaseFile; +#elif SDFAT_FILE_TYPE == 3 +typedef FsBaseFile StreamBaseFile; +#else // SDFAT_FILE_TYPE +#error Invalid SDFAT_FILE_TYPE +#endif // SDFAT_FILE_TYPE +/** + * \class ios_base + * \brief Base class for all streams + */ +class ios_base { + public: + /** typedef for iostate bitmask */ + typedef unsigned char iostate; + // State flags. + /** iostate for no flags */ + static const iostate goodbit = 0x00; + /** iostate bad bit for a nonrecoverable error. */ + static const iostate badbit = 0X01; + /** iostate bit for end of file reached */ + static const iostate eofbit = 0x02; + /** iostate fail bit for nonfatal error */ + static const iostate failbit = 0X04; +#if SDFAT_FILE_TYPE == 1 + /** + * unsigned size that can represent maximum file size. + * (violates spec - should be signed) + */ + typedef uint32_t streamsize; + /** type for absolute seek position */ + typedef uint32_t pos_type; + /** type for relative seek offset */ + typedef int32_t off_type; +#else // SDFAT_FILE_TYPE + /** + * unsigned size that can represent maximum file size. + * (violates spec - should be signed) + */ + typedef uint64_t streamsize; + /** type for absolute seek position */ + typedef uint64_t pos_type; + /** type for relative seek offset */ + typedef int64_t off_type; +#endif // SDFAT_FILE_TYPE + /** enumerated type for the direction of relative seeks */ + enum seekdir { + /** seek relative to the beginning of the stream */ + beg, + /** seek relative to the current stream position */ + cur, + /** seek relative to the end of the stream */ + end + }; + /** type for format flags */ + typedef unsigned int fmtflags; + /** left adjust fields */ + static const fmtflags left = 0x0001; + /** right adjust fields */ + static const fmtflags right = 0x0002; + /** fill between sign/base prefix and number */ + static const fmtflags internal = 0x0004; + /** base 10 flag*/ + static const fmtflags dec = 0x0008; + /** base 16 flag */ + static const fmtflags hex = 0x0010; + /** base 8 flag */ + static const fmtflags oct = 0x0020; + // static const fmtflags fixed = 0x0040; + // static const fmtflags scientific = 0x0080; + /** use strings true/false for bool */ + static const fmtflags boolalpha = 0x0100; + /** use prefix 0X for hex and 0 for oct */ + static const fmtflags showbase = 0x0200; + /** always show '.' for floating numbers */ + static const fmtflags showpoint = 0x0400; + /** show + sign for nonnegative numbers */ + static const fmtflags showpos = 0x0800; + /** skip initial white space */ + static const fmtflags skipws = 0x1000; + // static const fmtflags unitbuf = 0x2000; + /** use uppercase letters in number representations */ + static const fmtflags uppercase = 0x4000; + /** mask for adjustfield */ + static const fmtflags adjustfield = left | right | internal; + /** mask for basefield */ + static const fmtflags basefield = dec | hex | oct; + // static const fmtflags floatfield = scientific | fixed; + //---------------------------------------------------------------------------- + /** typedef for iostream open mode */ + typedef uint8_t openmode; + + // Openmode flags. + /** seek to end before each write */ + static const openmode app = 0X4; + /** open and seek to end immediately after opening */ + static const openmode ate = 0X8; + /** perform input and output in binary mode (as opposed to text mode) */ + static const openmode binary = 0X10; + /** open for input */ + static const openmode in = 0X20; + /** open for output */ + static const openmode out = 0X40; + /** truncate an existing stream when opening */ + static const openmode trunc = 0X80; + //---------------------------------------------------------------------------- + ios_base() + : m_fill(' '), + m_fmtflags(dec | right | skipws), + m_precision(2), + m_width(0) {} + /** \return fill character */ + char fill() { return m_fill; } + /** Set fill character + * \param[in] c new fill character + * \return old fill character + */ + char fill(char c) { + char r = m_fill; + m_fill = c; + return r; + } + /** \return format flags */ + fmtflags flags() const { return m_fmtflags; } + /** set format flags + * \param[in] fl new flag + * \return old flags + */ + fmtflags flags(fmtflags fl) { + fmtflags tmp = m_fmtflags; + m_fmtflags = fl; + return tmp; + } + /** \return precision */ + int precision() const { return m_precision; } + /** set precision + * \param[in] n new precision + * \return old precision + */ + int precision(unsigned int n) { + int r = m_precision; + m_precision = n; + return r; + } + /** set format flags + * \param[in] fl new flags to be or'ed in + * \return old flags + */ + fmtflags setf(fmtflags fl) { + fmtflags r = m_fmtflags; + m_fmtflags |= fl; + return r; + } + /** modify format flags + * \param[in] mask flags to be removed + * \param[in] fl flags to be set after mask bits have been cleared + * \return old flags + */ + fmtflags setf(fmtflags fl, fmtflags mask) { + fmtflags r = m_fmtflags; + m_fmtflags &= ~mask; + m_fmtflags |= fl; + return r; + } + /** clear format flags + * \param[in] fl flags to be cleared + */ + void unsetf(fmtflags fl) { m_fmtflags &= ~fl; } + /** \return width */ + unsigned width() { return m_width; } + /** set width + * \param[in] n new width + * \return old width + */ + unsigned width(unsigned n) { + unsigned r = m_width; + m_width = n; + return r; + } + + protected: + /** \return current number base */ + uint8_t flagsToBase() { + uint8_t f = flags() & basefield; + return f == oct ? 8 : f != hex ? 10 : 16; + } + + private: + char m_fill; + fmtflags m_fmtflags; + unsigned char m_precision; + unsigned int m_width; +}; +//------------------------------------------------------------------------------ +/** function for boolalpha manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& boolalpha(ios_base& str) { + str.setf(ios_base::boolalpha); + return str; +} +/** function for dec manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& dec(ios_base& str) { + str.setf(ios_base::dec, ios_base::basefield); + return str; +} +/** function for hex manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& hex(ios_base& str) { + str.setf(ios_base::hex, ios_base::basefield); + return str; +} +/** function for internal manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& internal(ios_base& str) { + str.setf(ios_base::internal, ios_base::adjustfield); + return str; +} +/** function for left manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& left(ios_base& str) { + str.setf(ios_base::left, ios_base::adjustfield); + return str; +} +/** function for noboolalpha manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noboolalpha(ios_base& str) { + str.unsetf(ios_base::boolalpha); + return str; +} +/** function for noshowbase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowbase(ios_base& str) { + str.unsetf(ios_base::showbase); + return str; +} +/** function for noshowpoint manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowpoint(ios_base& str) { + str.unsetf(ios_base::showpoint); + return str; +} +/** function for noshowpos manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowpos(ios_base& str) { + str.unsetf(ios_base::showpos); + return str; +} +/** function for noskipws manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noskipws(ios_base& str) { + str.unsetf(ios_base::skipws); + return str; +} +/** function for nouppercase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& nouppercase(ios_base& str) { + str.unsetf(ios_base::uppercase); + return str; +} +/** function for oct manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& oct(ios_base& str) { + str.setf(ios_base::oct, ios_base::basefield); + return str; +} +/** function for right manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& right(ios_base& str) { + str.setf(ios_base::right, ios_base::adjustfield); + return str; +} +/** function for showbase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showbase(ios_base& str) { + str.setf(ios_base::showbase); + return str; +} +/** function for showpos manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showpos(ios_base& str) { + str.setf(ios_base::showpos); + return str; +} +/** function for showpoint manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showpoint(ios_base& str) { + str.setf(ios_base::showpoint); + return str; +} +/** function for skipws manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& skipws(ios_base& str) { + str.setf(ios_base::skipws); + return str; +} +/** function for uppercase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& uppercase(ios_base& str) { + str.setf(ios_base::uppercase); + return str; +} +//============================================================================== +/** + * \class ios + * \brief Error and state information for all streams + */ +class ios : public ios_base { + public: + /** Create ios with no error flags set */ + ios() {} + + /** \return null pointer if fail() is true. */ + operator const void*() const { + return !fail() ? reinterpret_cast(this) : nullptr; + } + /** \return true if fail() else false. */ + bool operator!() const { return fail(); } + /** \return false if fail() else true. */ + explicit operator bool() const { return !fail(); } + /** \return The iostate flags for this file. */ + iostate rdstate() const { return m_iostate; } + /** \return True if no iostate flags are set else false. */ + bool good() const { return m_iostate == goodbit; } + /** \return true if end of file has been reached else false. + * + * Warning: An empty file returns false before the first read. + * + * Moral: eof() is only useful in combination with fail(), to find out + * whether EOF was the cause for failure + */ + bool eof() const { return m_iostate & eofbit; } + /** \return true if any iostate bit other than eof are set else false. */ + bool fail() const { return m_iostate & (failbit | badbit); } + /** \return true if bad bit is set else false. */ + bool bad() const { return m_iostate & badbit; } + /** Clear iostate bits. + * + * \param[in] state The flags you want to set after clearing all flags. + **/ + virtual void clear(iostate state = goodbit) { m_iostate = state; } + /** Set iostate bits. + * + * \param[in] state Bitts to set. + **/ + void setstate(iostate state) { m_iostate |= state; } + + private: + iostate m_iostate = 0; +}; diff --git a/third_party/sdfat/src/iostream/iostream.h b/third_party/sdfat/src/iostream/iostream.h new file mode 100644 index 00000000..dfb2b191 --- /dev/null +++ b/third_party/sdfat/src/iostream/iostream.h @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief \ref iostream class + */ +#pragma once +#include "istream.h" +#include "ostream.h" +/** Skip white space + * \param[in] is the Stream + * \return The stream + */ +inline istream &ws(istream &is) { + is.skipWhite(); + return is; +} +/** insert endline + * \param[in] os The Stream + * \return The stream + */ +inline ostream &endl(ostream &os) { + os.put('\n'); +#if ENDL_CALLS_FLUSH + os.flush(); +#endif // ENDL_CALLS_FLUSH + return os; +} +/** flush manipulator + * \param[in] os The stream + * \return The stream + */ +inline ostream &flush(ostream &os) { + os.flush(); + return os; +} +/** + * \struct setfill + * \brief type for setfill manipulator + */ +struct setfill { + /** fill character */ + char c; + /** constructor + * + * \param[in] arg new fill character + */ + explicit setfill(char arg) : c(arg) {} +}; +/** setfill manipulator + * \param[in] os the stream + * \param[in] arg set setfill object + * \return the stream + */ +inline ostream &operator<<(ostream &os, const setfill &arg) { + os.fill(arg.c); + return os; +} +/** setfill manipulator + * \param[in] obj the stream + * \param[in] arg set setfill object + * \return the stream + */ +inline istream &operator>>(istream &obj, const setfill &arg) { + obj.fill(arg.c); + return obj; +} +//------------------------------------------------------------------------------ +/** \struct setprecision + * \brief type for setprecision manipulator + */ +struct setprecision { + /** precision */ + unsigned int p; + /** constructor + * \param[in] arg new precision + */ + explicit setprecision(unsigned int arg) : p(arg) {} +}; +/** setprecision manipulator + * \param[in] os the stream + * \param[in] arg set setprecision object + * \return the stream + */ +inline ostream &operator<<(ostream &os, const setprecision &arg) { + os.precision(arg.p); + return os; +} +/** setprecision manipulator + * \param[in] is the stream + * \param[in] arg set setprecision object + * \return the stream + */ +inline istream &operator>>(istream &is, const setprecision &arg) { + is.precision(arg.p); + return is; +} +//------------------------------------------------------------------------------ +/** \struct setw + * \brief type for setw manipulator + */ +struct setw { + /** width */ + unsigned w; + /** constructor + * \param[in] arg new width + */ + explicit setw(unsigned arg) : w(arg) {} +}; +/** setw manipulator + * \param[in] os the stream + * \param[in] arg set setw object + * \return the stream + */ +inline ostream &operator<<(ostream &os, const setw &arg) { + os.width(arg.w); + return os; +} +/** setw manipulator + * \param[in] is the stream + * \param[in] arg set setw object + * \return the stream + */ +inline istream &operator>>(istream &is, const setw &arg) { + is.width(arg.w); + return is; +} +//============================================================================== +/** + * \class iostream + * \brief Input/Output stream + */ +class iostream : public istream, public ostream {}; diff --git a/third_party/sdfat/src/iostream/istream.cpp b/third_party/sdfat/src/iostream/istream.cpp new file mode 100644 index 00000000..b1d4b556 --- /dev/null +++ b/third_party/sdfat/src/iostream/istream.cpp @@ -0,0 +1,396 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "istream.h" + +#ifdef __AVR__ +#include +#endif // __AVR__ +#include +#include +//------------------------------------------------------------------------------ +int istream::get() { + int c; + m_gcount = 0; + c = getch(); + if (c < 0) { + setstate(failbit); + } else { + m_gcount = 1; + } + return c; +} +//------------------------------------------------------------------------------ +istream& istream::get(char& c) { + int tmp = get(); + if (tmp >= 0) { + c = tmp; + } + return *this; +} +//------------------------------------------------------------------------------ +istream& istream::get(char* str, streamsize n, char delim) { + pos_t pos; + m_gcount = 0; + while ((m_gcount + 1) < n) { + int c = getch(&pos); + if (c < 0) { + break; + } + if (c == delim) { + setpos(&pos); + break; + } + str[m_gcount++] = c; + } + if (n > 0) { + str[m_gcount] = '\0'; + } + if (m_gcount == 0) { + setstate(failbit); + } + return *this; +} +//------------------------------------------------------------------------------ +void istream::getBool(bool* b) { + if ((flags() & boolalpha) == 0) { + getNumber(b); + return; + } +#ifdef __AVR__ + PGM_P truePtr = PSTR("true"); + PGM_P falsePtr = PSTR("false"); +#else // __AVR__ + const char* truePtr = "true"; + const char* falsePtr = "false"; +#endif // __AVR + const uint8_t true_len = 4; + const uint8_t false_len = 5; + bool trueOk = true; + bool falseOk = true; + uint8_t i = 0; + int c = readSkip(); + while (1) { +#ifdef __AVR__ + falseOk = falseOk && c == pgm_read_byte(falsePtr + i); + trueOk = trueOk && c == pgm_read_byte(truePtr + i); +#else // __AVR__ + falseOk = falseOk && c == falsePtr[i]; + trueOk = trueOk && c == truePtr[i]; +#endif // __AVR__ + if (trueOk == false && falseOk == false) { + break; + } + i++; + if (trueOk && i == true_len) { + *b = true; + return; + } + if (falseOk && i == false_len) { + *b = false; + return; + } + c = getch(); + } + setstate(failbit); +} +//------------------------------------------------------------------------------ +void istream::getChar(char* ch) { + int16_t c = readSkip(); + if (c < 0) { + setstate(failbit); + } else { + *ch = c; + } +} +//------------------------------------------------------------------------------ +// +// http://www.exploringbinary.com/category/numbers-in-computers/ +// +int16_t const EXP_LIMIT = 100; +static const uint32_t uint32_max = static_cast(-1); +bool istream::getDouble(double* value) { + bool got_digit = false; + bool got_dot = false; + bool neg; + int16_t c; + bool expNeg = false; + int16_t exponet = 0; + int16_t fracExp = 0; + uint32_t frac = 0; + pos_t endPos; + double pow10; + double v; + + getpos(&endPos); + c = readSkip(); + neg = c == '-'; + if (c == '-' || c == '+') { + c = getch(); + } + while (1) { + if (isdigit(c)) { + got_digit = true; + if (frac < uint32_max / 10) { + frac = frac * 10 + (c - '0'); + if (got_dot) { + fracExp--; + } + } else { + if (!got_dot) { + fracExp++; + } + } + } else if (!got_dot && c == '.') { + got_dot = true; + } else { + break; + } + if (fracExp < -EXP_LIMIT || fracExp > EXP_LIMIT) { + goto fail; + } + c = getch(&endPos); + } + if (!got_digit) { + goto fail; + } + if (c == 'e' || c == 'E') { + c = getch(); + expNeg = c == '-'; + if (c == '-' || c == '+') { + c = getch(); + } + while (isdigit(c)) { + if (exponet > EXP_LIMIT) { + goto fail; + } + exponet = exponet * 10 + (c - '0'); + c = getch(&endPos); + } + } + v = static_cast(frac); + exponet = expNeg ? fracExp - exponet : fracExp + exponet; + expNeg = exponet < 0; + if (expNeg) { + exponet = -exponet; + } + pow10 = 10.0; + while (exponet) { + if (exponet & 1) { + if (expNeg) { + // check for underflow + if (v < DBL_MIN * pow10 && frac != 0) { + goto fail; + } + v /= pow10; + } else { + // check for overflow + if (v > DBL_MAX / pow10) { + goto fail; + } + v *= pow10; + } + } + pow10 *= pow10; + exponet >>= 1; + } + setpos(&endPos); + *value = neg ? -v : v; + return true; + +fail: + // error restore position to last good place + setpos(&endPos); + setstate(failbit); + return false; +} +//------------------------------------------------------------------------------ + +istream& istream::getline(char* str, streamsize n, char delim) { + pos_t pos; + m_gcount = 0; + if (n > 0) { + str[0] = '\0'; + } + while (1) { + int c = getch(&pos); + if (c < 0) { + break; + } + if (c == delim) { + m_gcount++; + break; + } + if ((m_gcount + 1) >= n) { + setpos(&pos); + setstate(failbit); + break; + } + str[m_gcount++] = c; + str[m_gcount] = '\0'; + } + if (m_gcount == 0) { + setstate(failbit); + } + return *this; +} +//------------------------------------------------------------------------------ +bool istream::getNumber(uint32_t posMax, uint32_t negMax, uint32_t* num) { + int16_t c; + int8_t any = 0; + int8_t have_zero = 0; + uint8_t neg; + uint32_t val = 0; + uint32_t cutoff; + uint8_t cutlim; + pos_t endPos; + uint8_t f = flags() & basefield; + uint8_t base = f == oct ? 8 : f != hex ? 10 : 16; + getpos(&endPos); + c = readSkip(); + + neg = c == '-' ? 1 : 0; + if (c == '-' || c == '+') { + c = getch(); + } + + if (base == 16 && c == '0') { // TESTSUITE + c = getch(&endPos); + if (c == 'X' || c == 'x') { + c = getch(); + // remember zero in case no hex digits follow x/X + have_zero = 1; + } else { + any = 1; + } + } + // set values for overflow test + cutoff = neg ? negMax : posMax; + cutlim = cutoff % base; + cutoff /= base; + + while (1) { + if (isdigit(c)) { + c -= '0'; + } else if (isalpha(c)) { + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + } else { + break; + } + if (c >= base) { + break; + } + if (val > cutoff || (val == cutoff && c > cutlim)) { + // indicate overflow error + any = -1; + break; + } + val = val * base + c; + c = getch(&endPos); + any = 1; + } + setpos(&endPos); + if (any > 0 || (have_zero && any >= 0)) { + *num = neg ? -val : val; + return true; + } + setstate(failbit); + return false; +} +//------------------------------------------------------------------------------ +void istream::getStr(char* str) { + pos_t pos; + uint16_t i = 0; + uint16_t m = width() ? width() - 1 : 0XFFFE; + if (m != 0) { + getpos(&pos); + int c = readSkip(); + + while (i < m) { + if (c < 0) { + break; + } + if (isspace(c)) { + setpos(&pos); + break; + } + str[i++] = c; + c = getch(&pos); + } + } + str[i] = '\0'; + if (i == 0) { + setstate(failbit); + } + width(0); +} +//------------------------------------------------------------------------------ +istream& istream::ignore(streamsize n, int delim) { + m_gcount = 0; + while (m_gcount < n) { + int c = getch(); + if (c < 0) { + break; + } + m_gcount++; + if (c == delim) { + break; + } + } + return *this; +} +//------------------------------------------------------------------------------ +int istream::peek() { + int16_t c; + pos_t pos; + m_gcount = 0; + getpos(&pos); + c = getch(); + if (c < 0) { + if (!bad()) { + setstate(eofbit); + } + } else { + setpos(&pos); + } + return c; +} +//------------------------------------------------------------------------------ +int16_t istream::readSkip() { + int16_t c; + do { + c = getch(); + } while (isspace(c) && (flags() & skipws)); + return c; +} +//------------------------------------------------------------------------------ +/** used to implement ws() */ +void istream::skipWhite() { + int c; + pos_t pos; + do { + c = getch(&pos); + } while (isspace(c)); + setpos(&pos); +} diff --git a/third_party/sdfat/src/iostream/istream.h b/third_party/sdfat/src/iostream/istream.h new file mode 100644 index 00000000..dc9eadb4 --- /dev/null +++ b/third_party/sdfat/src/iostream/istream.h @@ -0,0 +1,377 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief \ref istream class + */ +#include "ios.h" + +/** + * \class istream + * \brief Input Stream + */ +class istream : public virtual ios { + public: + istream() = default; + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(istream& (*pf)(istream& str)) { return pf(*this); } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(ios_base& (*pf)(ios_base& str)) { + pf(*this); + return *this; + } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(ios& (*pf)(ios& str)) { + pf(*this); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(char* str) { + getStr(str); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(char& ch) { + getChar(&ch); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(signed char* str) { + getStr(reinterpret_cast(str)); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(signed char& ch) { + getChar(reinterpret_cast(&ch)); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned char* str) { + getStr(reinterpret_cast(str)); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned char& ch) { + getChar(reinterpret_cast(&ch)); + return *this; + } + /** + * Extract a value of type bool. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(bool& arg) { + getBool(&arg); + return *this; + } + /** + * Extract a value of type short. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(short& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned short. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned short& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type int. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(int& arg) { + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned int. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned int& arg) { + getNumber(&arg); + return *this; + } + /** + * Extract a value of type long. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(long& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned long. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned long& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type double. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(double& arg) { + getDouble(&arg); + return *this; + } + /** + * Extract a value of type float. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(float& arg) { + double v; + getDouble(&v); + arg = v; + return *this; + } + /** + * Extract a value of type void*. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(void*& arg) { + uint32_t val; + getNumber(&val); + arg = reinterpret_cast(val); + return *this; + } + /** + * \return The number of characters extracted by the last unformatted + * input function. + */ + streamsize gcount() const { return m_gcount; } + /** + * Extract a character if one is available. + * + * \return The character or -1 if a failure occurs. A failure is indicated + * by the stream state. + */ + int get(); + /** + * Extract a character if one is available. + * + * \param[out] ch location to receive the extracted character. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& get(char& ch); + /** + * Extract characters. + * + * \param[out] str Location to receive extracted characters. + * \param[in] n Size of str. + * \param[in] delim Delimiter + * + * Characters are extracted until extraction fails, n is less than 1, + * n-1 characters are extracted, or the next character equals + * \a delim (delim is not extracted). If no characters are extracted + * failbit is set. If end-of-file occurs the eofbit is set. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& get(char* str, streamsize n, char delim = '\n'); + /** + * Extract characters + * + * \param[out] str Location to receive extracted characters. + * \param[in] n Size of str. + * \param[in] delim Delimiter + * + * Characters are extracted until extraction fails, + * the next character equals \a delim (delim is extracted), or n-1 + * characters are extracted. + * + * The failbit is set if no characters are extracted or n-1 characters + * are extracted. If end-of-file occurs the eofbit is set. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& getline(char* str, streamsize n, char delim = '\n'); + /** + * Extract characters and discard them. + * + * \param[in] n maximum number of characters to ignore. + * \param[in] delim Delimiter. + * + * Characters are extracted until extraction fails, \a n characters + * are extracted, or the next input character equals \a delim + * (the delimiter is extracted). If end-of-file occurs the eofbit is set. + * + * Failures are indicated by the state of the stream. + * + * \return *this + * + */ + istream& ignore(streamsize n = 1, int delim = -1); + /** + * Return the next available character without consuming it. + * + * \return The character if the stream state is good else -1; + * + */ + int peek(); + // istream& read(char *str, streamsize count); + // streamsize readsome(char *str, streamsize count); + /** + * \return the stream position + */ + pos_type tellg() { return tellpos(); } + /** + * Set the stream position + * \param[in] pos The absolute position in which to move the read pointer. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& seekg(pos_type pos) { + if (!seekpos(pos)) { + setstate(failbit); + } + return *this; + } + /** + * Set the stream position. + * + * \param[in] off An offset to move the read pointer relative to way. + * \a off is a signed 32-bit int so the offset is limited to +- 2GB. + * \param[in] way One of ios::beg, ios::cur, or ios::end. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& seekg(off_type off, seekdir way) { + if (!seekoff(off, way)) { + setstate(failbit); + } + return *this; + } + void skipWhite(); + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal - do not use + * \return + */ + virtual int16_t getch() = 0; + /** + * Internal - do not use + * \param[out] pos + * \return + */ + int16_t getch(pos_t* pos) { + getpos(pos); + return getch(); + } + /** + * Internal - do not use + * \param[out] pos + */ + virtual void getpos(pos_t* pos) = 0; + /** + * Internal - do not use + * \param[in] pos + */ + virtual bool seekoff(off_type off, seekdir way) = 0; + virtual bool seekpos(pos_type pos) = 0; + virtual void setpos(const pos_t* pos) = 0; + virtual pos_type tellpos() = 0; + + /// @endcond + private: + void getBool(bool* b); + void getChar(char* ch); + bool getDouble(double* value); + template + void getNumber(T* value); + bool getNumber(uint32_t posMax, uint32_t negMax, uint32_t* num); + void getStr(char* str); + int16_t readSkip(); + + size_t m_gcount = 0; +}; +//------------------------------------------------------------------------------ +template +void istream::getNumber(T* value) { + uint32_t tmp; + if ((T)-1 < 0) { + // number is signed, max positive value + uint32_t const m = (static_cast(-1)) >> (33 - sizeof(T) * 8); + // max absolute value of negative number is m + 1. + if (getNumber(m, m + 1, &tmp)) { + *value = (T)tmp; + } + } else { + // max unsigned value for T + uint32_t const m = (T)-1; + if (getNumber(m, m, &tmp)) { + *value = (T)tmp; + } + } +} diff --git a/third_party/sdfat/src/iostream/ostream.cpp b/third_party/sdfat/src/iostream/ostream.cpp new file mode 100644 index 00000000..7e308098 --- /dev/null +++ b/third_party/sdfat/src/iostream/ostream.cpp @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "ostream.h" +#ifdef __AVR__ +#include +#endif // __AVR__ +#include +#ifndef PSTR +#define PSTR(x) x +#endif // PSTR +//------------------------------------------------------------------------------ +void ostream::do_fill(unsigned len) { + for (; len < width(); len++) { + putch(fill()); + } + width(0); +} +//------------------------------------------------------------------------------ +void ostream::fill_not_left(unsigned len) { + if ((flags() & adjustfield) != left) { + do_fill(len); + } +} +//------------------------------------------------------------------------------ +void ostream::putBool(bool b) { + if (flags() & boolalpha) { + if (b) { + putPgm(PSTR("true")); + } else { + putPgm(PSTR("false")); + } + } else { + putChar(b ? '1' : '0'); + } +} +//------------------------------------------------------------------------------ +void ostream::putChar(char c) { + fill_not_left(1); + putch(c); + do_fill(1); +} +//------------------------------------------------------------------------------ +void ostream::putDouble(double n) { + uint8_t nd = precision(); + double roundDbl = 0.5; + char sign; + char buf[13]; // room for sign, 10 digits, '.', and zero byte + char *ptr = buf + sizeof(buf) - 1; + char *str = ptr; + // terminate string + *ptr = '\0'; + + // get sign and make nonnegative + if (n < 0.0) { + sign = '-'; + n = -n; + } else { + sign = flags() & showpos ? '+' : '\0'; + } + // check for larger than uint32_t + if (n > 4.0E9) { + putPgm(PSTR("BIG FLT")); + return; + } + // roundDbl up and separate int and fraction parts + for (uint8_t i = 0; i < nd; ++i) { + roundDbl *= 0.1; + } + n += roundDbl; + uint32_t intPart = n; + double fractionPart = n - intPart; + + // format intPart and decimal point + if (nd || (flags() & showpoint)) { + *--str = '.'; + } + str = fmtNum(intPart, str, 10); + + // calculate length for fill + uint8_t len = sign ? 1 : 0; + len += nd + ptr - str; + + // extract adjust field + fmtflags adj = flags() & adjustfield; + if (adj == internal) { + if (sign) { + putch(sign); + } + do_fill(len); + } else { + // do fill for right + fill_not_left(len); + if (sign) { + *--str = sign; + } + } + putstr(str); + // output fraction + while (nd-- > 0) { + fractionPart *= 10.0; + int digit = static_cast(fractionPart); + putch(digit + '0'); + fractionPart -= digit; + } + // do fill if not done above + do_fill(len); +} +//------------------------------------------------------------------------------ +void ostream::putNum(int32_t n) { + bool neg = n < 0 && flagsToBase() == 10; + putNum(static_cast(neg ? -n : n), neg); +} +//------------------------------------------------------------------------------ +void ostream::putNum(int64_t n) { + bool neg = n < 0 && flagsToBase() == 10; + putNum(static_cast(neg ? -n : n), neg); +} +//------------------------------------------------------------------------------ +void ostream::putPgm(const char *str) { +#ifndef __AVR__ + putStr(str); +#else // __AVR__ + uint8_t c; + int n; + for (n = 0; pgm_read_byte(&str[n]); n++) { + } + fill_not_left(n); + for (n = 0; (c = pgm_read_byte(&str[n])); n++) { + putch(c); + } + do_fill(n); +#endif // __AVR__ +} +//------------------------------------------------------------------------------ +void ostream::putStr(const char *str) { + unsigned n = strlen(str); + fill_not_left(n); + putstr(str); + do_fill(n); +} diff --git a/third_party/sdfat/src/iostream/ostream.h b/third_party/sdfat/src/iostream/ostream.h new file mode 100644 index 00000000..f70d6eb1 --- /dev/null +++ b/third_party/sdfat/src/iostream/ostream.h @@ -0,0 +1,342 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief \ref ostream class + */ +#include "ios.h" +//============================================================================== +/** + * \class ostream + * \brief Output Stream + */ +class ostream : public virtual ios { + public: + ostream() {} + + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + ostream &operator<<(ostream &(*pf)(ostream &str)) { return pf(*this); } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + ostream &operator<<(ios_base &(*pf)(ios_base &str)) { + pf(*this); + return *this; + } + /** Output bool + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(bool arg) { + putBool(arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<<(const char *arg) { + putStr(arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<<(const signed char *arg) { + putStr(reinterpret_cast(arg)); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<<(const unsigned char *arg) { + putStr(reinterpret_cast(arg)); + return *this; + } +#if ENABLE_ARDUINO_STRING + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<<(const String &arg) { + putStr(arg.c_str()); + return *this; + } +#endif // ENABLE_ARDUINO_STRING + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<<(char arg) { + putChar(arg); + return *this; + } + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<<(signed char arg) { + putChar(static_cast(arg)); + return *this; + } + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<<(unsigned char arg) { + putChar(static_cast(arg)); + return *this; + } + /** Output double + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(double arg) { + putDouble(arg); + return *this; + } + /** Output float + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(float arg) { + putDouble(arg); + return *this; + } + /** Output signed short + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(short arg) { // NOLINT + putNum(static_cast(arg)); + return *this; + } + /** Output unsigned short + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(unsigned short arg) { // NOLINT + putNum(static_cast(arg)); + return *this; + } + /** Output signed int + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(int arg) { + putNum(static_cast(arg)); + return *this; + } + /** Output unsigned int + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(unsigned int arg) { + putNum(static_cast(arg)); + return *this; + } + /** Output signed long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(long arg) { // NOLINT + putNum(static_cast(arg)); + return *this; + } + /** Output unsigned long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(unsigned long arg) { // NOLINT + putNum(static_cast(arg)); + return *this; + } + /** Output signed long long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(long long arg) { // NOLINT + putNum(static_cast(arg)); + return *this; + } + /** Output unsigned long long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(unsigned long long arg) { // NOLINT + putNum(static_cast(arg)); + return *this; + } + /** Output pointer + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<<(const void *arg) { + putNum(reinterpret_cast(arg)); + return *this; + } + /** Output a string from flash using the Arduino F() macro. + * \param[in] arg pointing to flash string + * \return the stream + */ + ostream &operator<<(const __FlashStringHelper *arg) { + putPgm(reinterpret_cast(arg)); + return *this; + } + /** + * Puts a character in a stream. + * + * The unformatted output function inserts the element \a ch. + * It returns *this. + * + * \param[in] ch The character + * \return A reference to the ostream object. + */ + ostream &put(char ch) { + putch(ch); + return *this; + } + // ostream& write(char *str, streamsize count); + /** + * Flushes the buffer associated with this stream. The flush function + * calls the sync function of the associated file. + * \return A reference to the ostream object. + */ + ostream &flush() { + if (!sync()) { + setstate(badbit); + } + return *this; + } + /** + * \return the stream position + */ + pos_type tellp() { return tellpos(); } + /** + * Set the stream position + * \param[in] pos The absolute position in which to move the write pointer. + * \return Is always *this. Failure is indicated by the state of *this. + */ + ostream &seekp(pos_type pos) { + if (!seekpos(pos)) { + setstate(failbit); + } + return *this; + } + /** + * Set the stream position. + * + * \param[in] off An offset to move the write pointer relative to way. + * \a off is a signed 32-bit int so the offset is limited to +- 2GB. + * \param[in] way One of ios::beg, ios::cur, or ios::end. + * \return Is always *this. Failure is indicated by the state of *this. + */ + ostream &seekp(off_type off, seekdir way) { + if (!seekoff(off, way)) { + setstate(failbit); + } + return *this; + } + + protected: + /// @cond SHOW_PROTECTED + /** Put character with binary/text conversion + * \param[in] ch character to write + */ + virtual void putch(char ch) = 0; + virtual void putstr(const char *str) = 0; + virtual bool seekoff(off_type pos, seekdir way) = 0; + virtual bool seekpos(pos_type pos) = 0; + virtual bool sync() = 0; + virtual pos_type tellpos() = 0; + /// @endcond + private: + void do_fill(unsigned len); + void fill_not_left(unsigned len); + void putBool(bool b); + void putChar(char c); + void putDouble(double n); + void putNum(int32_t n); + void putNum(int64_t n); + void putNum(uint32_t n) { putNum(n, false); } + void putNum(uint64_t n) { putNum(n, false); } + void putPgm(const char *str); + void putStr(const char *str); + + template + char *fmtNum(T n, char *ptr, uint8_t base) { + char a = (flags() & uppercase) ? 'A' - 10 : 'a' - 10; + do { + T m = n; + n /= base; + char c = m - base * n; + *--ptr = c < 10 ? c + '0' : c + a; + } while (n); + return ptr; + } + + template + void putNum(T n, bool neg) { + char buf[(8 * sizeof(T) + 2) / 3 + 2]; + char *ptr = buf + sizeof(buf) - 1; + char *num; + char *str; + uint8_t base = flagsToBase(); + *ptr = '\0'; + str = num = fmtNum(n, ptr, base); + if (base == 10) { + if (neg) { + *--str = '-'; + } else if (flags() & showpos) { + *--str = '+'; + } + } else if (flags() & showbase) { + if (flags() & hex) { + *--str = flags() & uppercase ? 'X' : 'x'; + } + *--str = '0'; + } + uint8_t len = ptr - str; + fmtflags adj = flags() & adjustfield; + if (adj == internal) { + while (str < num) { + putch(*str++); + } + do_fill(len); + } else { + // do fill for right + fill_not_left(len); + } + putstr(str); + do_fill(len); + } +}; diff --git a/third_party/sdfat/src/sdios.h b/third_party/sdfat/src/sdios.h new file mode 100644 index 00000000..2d867502 --- /dev/null +++ b/third_party/sdfat/src/sdios.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2011-2025 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \file + * \brief C++ IO Streams features. + */ +#include "iostream/ArduinoStream.h" +#include "iostream/StdioStream.h" +#include "iostream/fstream.h"