From 901925ebc5ac5c58a3537e4949bc69c6f97d54b8 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Thu, 6 Aug 2020 13:51:58 +0800 Subject: [PATCH 1/5] CMake: Fix issue in newer cmake versions * Fixes following error: can not determine linker language for target: __idf_newlib --- tools/cmake/component.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cmake/component.cmake b/tools/cmake/component.cmake index 4a7396501e..1669fac2ce 100644 --- a/tools/cmake/component.cmake +++ b/tools/cmake/component.cmake @@ -475,7 +475,7 @@ function(idf_component_register) __component_add_include_dirs(${component_lib} "${__INCLUDE_DIRS}" PUBLIC) __component_add_include_dirs(${component_lib} "${__PRIV_INCLUDE_DIRS}" PRIVATE) __component_add_include_dirs(${component_lib} "${config_dir}" PUBLIC) - set_target_properties(${component_lib} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME}) + set_target_properties(${component_lib} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME} LINKER_LANGUAGE C) __ldgen_add_component(${component_lib}) else() add_library(${component_lib} INTERFACE) From 52093fa4efa8489e79a1d068de5ec0e5c1e12b81 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Mon, 10 Aug 2020 12:29:05 +0800 Subject: [PATCH 2/5] linux: added linux target * add toolchain file * add linux to preview targets * add stub for dfu number in cmake * excluded unity runner per default * Added esp_attr.h and esp_partition.h linux stubs * component.cmake check list for emptyness * added switch for linux in unity cmake file * Added Linux host example app --- .../sim/stubs/spi_flash/esp_partition.h | 95 +++++++++++ .../spi_flash/sim/stubs/xtensa/esp_attr.h | 149 ++++++++++++++++++ components/unity/CMakeLists.txt | 24 ++- .../cmake/linux_host_app/CMakeLists.txt | 5 + .../cmake/linux_host_app/README.md | 8 + .../cmake/linux_host_app/main/CMakeLists.txt | 1 + .../linux_host_app/main/linux_host_app.cpp | 20 +++ .../cmake/linux_host_app/sdkconfig.defaults | 2 + tools/cmake/build.cmake | 24 ++- tools/cmake/component.cmake | 4 +- tools/cmake/dfu.cmake | 2 + tools/cmake/toolchain-linux.cmake | 8 + tools/idf_py_actions/constants.py | 2 +- 13 files changed, 330 insertions(+), 14 deletions(-) create mode 100644 components/spi_flash/sim/stubs/spi_flash/esp_partition.h create mode 100644 components/spi_flash/sim/stubs/xtensa/esp_attr.h create mode 100644 examples/build_system/cmake/linux_host_app/CMakeLists.txt create mode 100644 examples/build_system/cmake/linux_host_app/README.md create mode 100644 examples/build_system/cmake/linux_host_app/main/CMakeLists.txt create mode 100644 examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp create mode 100644 examples/build_system/cmake/linux_host_app/sdkconfig.defaults create mode 100644 tools/cmake/toolchain-linux.cmake diff --git a/components/spi_flash/sim/stubs/spi_flash/esp_partition.h b/components/spi_flash/sim/stubs/spi_flash/esp_partition.h new file mode 100644 index 0000000000..824938760c --- /dev/null +++ b/components/spi_flash/sim/stubs/spi_flash/esp_partition.h @@ -0,0 +1,95 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_PARTITION_H__ +#define __ESP_PARTITION_H__ + +#include +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ESP_PARTITION_TYPE_APP = 0x00, //!< Application partition type + ESP_PARTITION_TYPE_DATA = 0x01, //!< Data partition type +} esp_partition_type_t; + +typedef enum { + ESP_PARTITION_SUBTYPE_APP_FACTORY = 0x00, //!< Factory application partition + ESP_PARTITION_SUBTYPE_APP_OTA_MIN = 0x10, //!< Base for OTA partition subtypes + ESP_PARTITION_SUBTYPE_APP_OTA_0 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 0, //!< OTA partition 0 + ESP_PARTITION_SUBTYPE_APP_OTA_1 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 1, //!< OTA partition 1 + ESP_PARTITION_SUBTYPE_APP_OTA_2 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 2, //!< OTA partition 2 + ESP_PARTITION_SUBTYPE_APP_OTA_3 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 3, //!< OTA partition 3 + ESP_PARTITION_SUBTYPE_APP_OTA_4 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 4, //!< OTA partition 4 + ESP_PARTITION_SUBTYPE_APP_OTA_5 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 5, //!< OTA partition 5 + ESP_PARTITION_SUBTYPE_APP_OTA_6 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 6, //!< OTA partition 6 + ESP_PARTITION_SUBTYPE_APP_OTA_7 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 7, //!< OTA partition 7 + ESP_PARTITION_SUBTYPE_APP_OTA_8 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 8, //!< OTA partition 8 + ESP_PARTITION_SUBTYPE_APP_OTA_9 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 9, //!< OTA partition 9 + ESP_PARTITION_SUBTYPE_APP_OTA_10 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 10,//!< OTA partition 10 + ESP_PARTITION_SUBTYPE_APP_OTA_11 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 11,//!< OTA partition 11 + ESP_PARTITION_SUBTYPE_APP_OTA_12 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 12,//!< OTA partition 12 + ESP_PARTITION_SUBTYPE_APP_OTA_13 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 13,//!< OTA partition 13 + ESP_PARTITION_SUBTYPE_APP_OTA_14 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 14,//!< OTA partition 14 + ESP_PARTITION_SUBTYPE_APP_OTA_15 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 15,//!< OTA partition 15 + ESP_PARTITION_SUBTYPE_APP_OTA_MAX = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 16,//!< Max subtype of OTA partition + ESP_PARTITION_SUBTYPE_APP_TEST = 0x20, //!< Test application partition + + ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00, //!< OTA selection partition + ESP_PARTITION_SUBTYPE_DATA_PHY = 0x01, //!< PHY init data partition + ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02, //!< NVS partition + ESP_PARTITION_SUBTYPE_DATA_COREDUMP = 0x03, //!< COREDUMP partition + ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS = 0x04, //!< Partition for NVS keys + ESP_PARTITION_SUBTYPE_DATA_EFUSE_EM = 0x05, //!< Partition for emulate eFuse bits + + ESP_PARTITION_SUBTYPE_DATA_ESPHTTPD = 0x80, //!< ESPHTTPD partition + ESP_PARTITION_SUBTYPE_DATA_FAT = 0x81, //!< FAT partition + ESP_PARTITION_SUBTYPE_DATA_SPIFFS = 0x82, //!< SPIFFS partition + + ESP_PARTITION_SUBTYPE_ANY = 0xff, //!< Used to search for partitions with any subtype +} esp_partition_subtype_t; + +/** + * @brief Opaque partition iterator type + */ +typedef struct esp_partition_iterator_opaque_* esp_partition_iterator_t; + +/** + * @brief partition information structure + * + * This is not the format in flash, that format is esp_partition_info_t. + * + * However, this is the format used by this API. + */ +typedef struct { + void* flash_chip; /*!< SPI flash chip on which the partition resides */ + esp_partition_type_t type; /*!< partition type (app/data) */ + esp_partition_subtype_t subtype; /*!< partition subtype */ + uint32_t address; /*!< starting address of the partition in flash */ + uint32_t size; /*!< size of the partition, in bytes */ + char label[17]; /*!< partition label, zero-terminated ASCII string */ + bool encrypted; /*!< flag is set to true if partition is encrypted */ +} esp_partition_t; + +#ifdef __cplusplus +} +#endif + + +#endif /* __ESP_PARTITION_H__ */ diff --git a/components/spi_flash/sim/stubs/xtensa/esp_attr.h b/components/spi_flash/sim/stubs/xtensa/esp_attr.h new file mode 100644 index 0000000000..2b59f16e95 --- /dev/null +++ b/components/spi_flash/sim/stubs/xtensa/esp_attr.h @@ -0,0 +1,149 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __ESP_ATTR_H__ +#define __ESP_ATTR_H__ + +#include "sdkconfig.h" + +#define ROMFN_ATTR + +//Normally, the linker script will put all code and rodata in flash, +//and all variables in shared RAM. These macros can be used to redirect +//particular functions/variables to other memory regions. + +// Forces code into IRAM instead of flash +#define IRAM_ATTR _SECTION_ATTR_IMPL(".iram1", __COUNTER__) + +// Forces data into DRAM instead of flash +#define DRAM_ATTR _SECTION_ATTR_IMPL(".dram1", __COUNTER__) + +#ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY +// Forces data into IRAM instead of DRAM +#define IRAM_DATA_ATTR __attribute__((section(".iram.data"))) + +// Forces data into IRAM instead of DRAM and map it to coredump +#define COREDUMP_IRAM_DATA_ATTR _SECTION_ATTR_IMPL(".iram.data.coredump", __COUNTER__) + +// Forces bss into IRAM instead of DRAM +#define IRAM_BSS_ATTR __attribute__((section(".iram.bss"))) +#else +#define COREDUMP_IRAM_DATA_ATTR +#define IRAM_DATA_ATTR + +#define IRAM_BSS_ATTR +#endif + +// Forces data to be 4 bytes aligned +#define WORD_ALIGNED_ATTR __attribute__((aligned(4))) + +// Forces data to be placed to DMA-capable places +#define DMA_ATTR WORD_ALIGNED_ATTR DRAM_ATTR + +// Forces a function to be inlined +#define FORCE_INLINE_ATTR static inline __attribute__((always_inline)) + +// Forces a string into DRAM instead of flash +// Use as esp_rom_printf(DRAM_STR("Hello world!\n")); +#define DRAM_STR(str) (__extension__({static const DRAM_ATTR char __c[] = (str); (const char *)&__c;})) + +// Forces code into RTC fast memory. See "docs/deep-sleep-stub.rst" +#define RTC_IRAM_ATTR _SECTION_ATTR_IMPL(".rtc.text", __COUNTER__) + +#if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY +// Forces bss variable into external memory. " +#define EXT_RAM_ATTR _SECTION_ATTR_IMPL(".ext_ram.bss", __COUNTER__) +#else +#define EXT_RAM_ATTR +#endif + +// Forces data into RTC slow memory. See "docs/deep-sleep-stub.rst" +// Any variable marked with this attribute will keep its value +// during a deep sleep / wake cycle. +#define RTC_DATA_ATTR _SECTION_ATTR_IMPL(".rtc.data", __COUNTER__) + +// Forces read-only data into RTC memory. See "docs/deep-sleep-stub.rst" +#define RTC_RODATA_ATTR _SECTION_ATTR_IMPL(".rtc.rodata", __COUNTER__) + +// Allows to place data into RTC_SLOW memory. +#define RTC_SLOW_ATTR _SECTION_ATTR_IMPL(".rtc.force_slow", __COUNTER__) + +// Allows to place data into RTC_FAST memory. +#define RTC_FAST_ATTR _SECTION_ATTR_IMPL(".rtc.force_fast", __COUNTER__) + +// Forces data into noinit section to avoid initialization after restart. +#define __NOINIT_ATTR _SECTION_ATTR_IMPL(".noinit", __COUNTER__) + +// Forces data into RTC slow memory of .noinit section. +// Any variable marked with this attribute will keep its value +// after restart or during a deep sleep / wake cycle. +#define RTC_NOINIT_ATTR _SECTION_ATTR_IMPL(".rtc_noinit", __COUNTER__) + +// Forces code into DRAM instead of flash and map it to coredump +#define COREDUMP_DRAM_ATTR _SECTION_ATTR_IMPL(".dram1.coredump", __COUNTER__) + +// Forces data into RTC memory and map it to coredump +#define COREDUMP_RTC_DATA_ATTR _SECTION_ATTR_IMPL(".rtc.coredump", __COUNTER__) + +// Allows to place data into RTC_FAST memory and map it to coredump +#define COREDUMP_RTC_FAST_ATTR _SECTION_ATTR_IMPL(".rtc.fast.coredump", __COUNTER__) + +// Forces to not inline function +#define NOINLINE_ATTR __attribute__((noinline)) + +// This allows using enum as flags in C++ +// Format: FLAG_ATTR(flag_enum_t) +#ifdef __cplusplus + +// Inline is required here to avoid multiple definition error in linker +#define FLAG_ATTR_IMPL(TYPE, INT_TYPE) \ +FORCE_INLINE_ATTR constexpr TYPE operator~ (TYPE a) { return (TYPE)~(INT_TYPE)a; } \ +FORCE_INLINE_ATTR constexpr TYPE operator| (TYPE a, TYPE b) { return (TYPE)((INT_TYPE)a | (INT_TYPE)b); } \ +FORCE_INLINE_ATTR constexpr TYPE operator& (TYPE a, TYPE b) { return (TYPE)((INT_TYPE)a & (INT_TYPE)b); } \ +FORCE_INLINE_ATTR constexpr TYPE operator^ (TYPE a, TYPE b) { return (TYPE)((INT_TYPE)a ^ (INT_TYPE)b); } \ +FORCE_INLINE_ATTR constexpr TYPE operator>> (TYPE a, int b) { return (TYPE)((INT_TYPE)a >> b); } \ +FORCE_INLINE_ATTR constexpr TYPE operator<< (TYPE a, int b) { return (TYPE)((INT_TYPE)a << b); } \ +FORCE_INLINE_ATTR TYPE& operator|=(TYPE& a, TYPE b) { a = a | b; return a; } \ +FORCE_INLINE_ATTR TYPE& operator&=(TYPE& a, TYPE b) { a = a & b; return a; } \ +FORCE_INLINE_ATTR TYPE& operator^=(TYPE& a, TYPE b) { a = a ^ b; return a; } \ +FORCE_INLINE_ATTR TYPE& operator>>=(TYPE& a, int b) { a >>= b; return a; } \ +FORCE_INLINE_ATTR TYPE& operator<<=(TYPE& a, int b) { a <<= b; return a; } + +#define FLAG_ATTR_U32(TYPE) FLAG_ATTR_IMPL(TYPE, uint32_t) +#define FLAG_ATTR FLAG_ATTR_U32 + +#else +#define FLAG_ATTR(TYPE) +#endif + +// Implementation for a unique custom section +// +// This prevents gcc producing "x causes a section type conflict with y" +// errors if two variables in the same source file have different linkage (maybe const & non-const) but are placed in the same custom section +// +// Using unique sections also means --gc-sections can remove unused +// data with a custom section type set +#define _SECTION_ATTR_IMPL(SECTION, COUNTER) __attribute__((section(SECTION "." _COUNTER_STRINGIFY(COUNTER)))) + +#define _COUNTER_STRINGIFY(COUNTER) #COUNTER + +/* Use IDF_DEPRECATED attribute to mark anything deprecated from use in + ESP-IDF's own source code, but not deprecated for external users. +*/ +#ifdef IDF_CI_BUILD +#define IDF_DEPRECATED(REASON) __attribute__((deprecated(REASON))) +#else +#define IDF_DEPRECATED(REASON) +#endif + +#endif /* __ESP_ATTR_H__ */ diff --git a/components/unity/CMakeLists.txt b/components/unity/CMakeLists.txt index ece69db6df..71a9bef47f 100644 --- a/components/unity/CMakeLists.txt +++ b/components/unity/CMakeLists.txt @@ -1,6 +1,9 @@ set(srcs - "unity/src/unity.c" - "unity_port_esp32.c") + "unity/src/unity.c") + +set(includes + "include" + "unity/src") if(CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL) list(APPEND COMPONENT_PRIV_INCLUDEDIRS "include/priv") @@ -12,13 +15,24 @@ endif() if(CONFIG_UNITY_ENABLE_FIXTURE) list(APPEND srcs "unity/extras/fixture/src/unity_fixture.c") + list(APPEND includes "unity/extras/fixture/src") +endif() + +if(${IDF_TARGET} STREQUAL "linux") + message(STATUS "adding linux stuff...") + idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR) + list(APPEND includes "${spi_flash_dir}/sim/stubs/esp_common") +else() + list(APPEND srcs "unity_port_esp32.c") endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "include" "unity/src" "unity/extras/fixture/src") -target_compile_definitions(${COMPONENT_LIB} PUBLIC - -DUNITY_INCLUDE_CONFIG_H -) +if(NOT ${IDF_TARGET} STREQUAL "linux") + target_compile_definitions(${COMPONENT_LIB} PUBLIC + -DUNITY_INCLUDE_CONFIG_H + ) +endif() target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable) diff --git a/examples/build_system/cmake/linux_host_app/CMakeLists.txt b/examples/build_system/cmake/linux_host_app/CMakeLists.txt new file mode 100644 index 0000000000..40264e1a7a --- /dev/null +++ b/examples/build_system/cmake/linux_host_app/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +project(linux_host_app) diff --git a/examples/build_system/cmake/linux_host_app/README.md b/examples/build_system/cmake/linux_host_app/README.md new file mode 100644 index 0000000000..1007777517 --- /dev/null +++ b/examples/build_system/cmake/linux_host_app/README.md @@ -0,0 +1,8 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# Build +`idf.py build` (sdkconfig.defaults sets the linux target by default) + +# Run +`build/linux_host_app.elf` diff --git a/examples/build_system/cmake/linux_host_app/main/CMakeLists.txt b/examples/build_system/cmake/linux_host_app/main/CMakeLists.txt new file mode 100644 index 0000000000..25db9e0ab7 --- /dev/null +++ b/examples/build_system/cmake/linux_host_app/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "linux_host_app.cpp") diff --git a/examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp b/examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp new file mode 100644 index 0000000000..c51a9fb764 --- /dev/null +++ b/examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp @@ -0,0 +1,20 @@ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "stdio.h" + +void app_main() { + printf("Hello, Host!\n"); +} + +int main(int argc, char **argv) +{ + app_main(); + return 0; +} diff --git a/examples/build_system/cmake/linux_host_app/sdkconfig.defaults b/examples/build_system/cmake/linux_host_app/sdkconfig.defaults new file mode 100644 index 0000000000..517e112e61 --- /dev/null +++ b/examples/build_system/cmake/linux_host_app/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="linux" +CONFIG_CXX_EXCEPTIONS=y diff --git a/tools/cmake/build.cmake b/tools/cmake/build.cmake index d9fd06258d..e6063d7d9c 100644 --- a/tools/cmake/build.cmake +++ b/tools/cmake/build.cmake @@ -151,12 +151,16 @@ function(__build_init idf_path) endif() endforeach() - # Set components required by all other components in the build - # - # - lwip is here so that #include works without any special provisions - # - esp_hw_support is here for backward compatibility - set(requires_common cxx newlib freertos esp_hw_support heap log lwip soc hal esp_rom esp_common esp_system) - idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}") + + idf_build_get_property(target IDF_TARGET) + if(NOT target STREQUAL "linux") + # Set components required by all other components in the build + # + # - lwip is here so that #include works without any special provisions + # - esp_hw_support is here for backward compatibility + set(requires_common cxx newlib freertos esp_hw_support heap log lwip soc hal esp_rom esp_common esp_system) + idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}") + endif() __build_get_idf_git_revision() __kconfig_init() @@ -397,7 +401,13 @@ macro(idf_build_process target) # Check for required Python modules __build_check_python() - idf_build_set_property(__COMPONENT_REQUIRES_COMMON ${target} APPEND) + idf_build_get_property(target IDF_TARGET) + + if(NOT target STREQUAL "linux") + idf_build_set_property(__COMPONENT_REQUIRES_COMMON ${target} APPEND) + else() + idf_build_set_property(__COMPONENT_REQUIRES_COMMON "") + endif() # Perform early expansion of component CMakeLists.txt in CMake scripting mode. # It is here we retrieve the public and private requirements of each component. diff --git a/tools/cmake/component.cmake b/tools/cmake/component.cmake index 1669fac2ce..47e3f21594 100644 --- a/tools/cmake/component.cmake +++ b/tools/cmake/component.cmake @@ -463,7 +463,9 @@ function(idf_component_register) idf_build_get_property(compile_definitions COMPILE_DEFINITIONS GENERATOR_EXPRESSION) add_compile_options("${compile_definitions}") - list(REMOVE_ITEM common_reqs ${component_lib}) + if(common_reqs) # check whether common_reqs exists, this may be the case in minimalistic host unit test builds + list(REMOVE_ITEM common_reqs ${component_lib}) + endif() link_libraries(${common_reqs}) idf_build_get_property(config_dir CONFIG_DIR) diff --git a/tools/cmake/dfu.cmake b/tools/cmake/dfu.cmake index e3199bb211..ce36656d72 100644 --- a/tools/cmake/dfu.cmake +++ b/tools/cmake/dfu.cmake @@ -9,6 +9,8 @@ function(__add_dfu_targets) set(dfu_pid "2") elseif("${target}" STREQUAL "esp32s3") set(dfu_pid "4") + elseif("${target}" STREQUAL "linux") + return() else() message(FATAL_ERROR "DFU PID unknown for ${target}") endif() diff --git a/tools/cmake/toolchain-linux.cmake b/tools/cmake/toolchain-linux.cmake new file mode 100644 index 0000000000..4c17215281 --- /dev/null +++ b/tools/cmake/toolchain-linux.cmake @@ -0,0 +1,8 @@ +set(CMAKE_SYSTEM_NAME Generic) + +set(CMAKE_C_COMPILER gcc) +set(CMAKE_CXX_COMPILER g++) +set(CMAKE_ASM_COMPILER gcc) + +set(CMAKE_C_FLAGS "-Wno-frame-address" CACHE STRING "C Compiler Base Flags") +set(CMAKE_CXX_FLAGS "-Wno-frame-address" CACHE STRING "C++ Compiler Base Flags") diff --git a/tools/idf_py_actions/constants.py b/tools/idf_py_actions/constants.py index 85baaee2f5..d4a96eaace 100644 --- a/tools/idf_py_actions/constants.py +++ b/tools/idf_py_actions/constants.py @@ -38,4 +38,4 @@ GENERATORS = collections.OrderedDict([ SUPPORTED_TARGETS = ["esp32", "esp32s2"] -PREVIEW_TARGETS = ["esp32s3"] +PREVIEW_TARGETS = ["esp32s3", "linux"] From c233ce0449b75ba9d56d2eb963b018a430694d64 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Tue, 1 Sep 2020 17:06:22 +0800 Subject: [PATCH 3/5] spi_flash: mocking should be possible now The following three headers will be mockes: * esp_flash.h * esp_spi_flash.h * esp_partition.h * counter functions live in own header * add spi_flash sim dir for esp_err.h to Unity * modified gen_esp_err_to_name.py to ignore sim/ dir in spi_flash component Add cmock .yaml config file Add spi hal header until soc can mock the hal layer as well. --- components/spi_flash/CMakeLists.txt | 170 ++++++++++++------ components/spi_flash/include/esp_spi_flash.h | 38 +--- .../include/esp_spi_flash_counters.h | 66 +++++++ components/spi_flash/mock/mock_config.yaml | 7 + .../spi_flash/sim/stubs/esp_common/esp_err.h | 149 +++++++++++++++ .../stubs/soc/include/hal/spi_flash_types.h | 148 +++++++++++++++ components/unity/CMakeLists.txt | 2 +- tools/gen_esp_err_to_name.py | 4 +- 8 files changed, 491 insertions(+), 93 deletions(-) create mode 100644 components/spi_flash/include/esp_spi_flash_counters.h create mode 100644 components/spi_flash/mock/mock_config.yaml create mode 100644 components/spi_flash/sim/stubs/esp_common/esp_err.h create mode 100644 components/spi_flash/sim/stubs/soc/include/hal/spi_flash_types.h diff --git a/components/spi_flash/CMakeLists.txt b/components/spi_flash/CMakeLists.txt index 78b536774b..ab0aef765f 100644 --- a/components/spi_flash/CMakeLists.txt +++ b/components/spi_flash/CMakeLists.txt @@ -1,63 +1,125 @@ -if(BOOTLOADER_BUILD) - if(CONFIG_IDF_TARGET_ESP32) - # ESP32 Bootloader needs SPIUnlock from this file, but doesn't - # need other parts of this component - set(srcs "esp32/spi_flash_rom_patch.c") - elseif(CONFIG_IDF_TARGET_ESP32S2) - set(srcs "esp32s2/spi_flash_rom_patch.c") - elseif(CONFIG_IDF_TARGET_ESP32S3) - set(srcs "esp32s3/spi_flash_rom_patch.c") +idf_build_get_property(spi_flash_mock CONFIG_SPI_FLASH_MOCK) +idf_build_get_property(target IDF_TARGET) +if(${spi_flash_mock}) + + message(STATUS "building SPI FLASH MOCKS") + + set(IDF_PATH $ENV{IDF_PATH}) + set(CMOCK_DIR "${IDF_PATH}/components/cmock/CMock") + set(MOCK_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/mocks") + + file(MAKE_DIRECTORY ${MOCK_GEN_DIR}) + + set(MOCK_OUTPUT + "${MOCK_GEN_DIR}/Mockesp_partition.c" "${MOCK_GEN_DIR}/Mockesp_partition.h" + "${MOCK_GEN_DIR}/Mockesp_flash.c" "${MOCK_GEN_DIR}/Mockesp_flash.h" + "${MOCK_GEN_DIR}/Mockesp_spi_flash.c" "${MOCK_GEN_DIR}/Mockesp_spi_flash.h") + + set(MOCK_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/include/esp_partition.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/esp_flash.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/esp_spi_flash.h + ) + + set(ENV{UNITY_DIR} "$ENV{IDF_PATH}/components/cmock/CMock") + + set(include_dirs + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${MOCK_GEN_DIR}") + + set(srcs "${MOCK_GEN_DIR}/Mockesp_partition.c" + "${MOCK_GEN_DIR}/Mockesp_spi_flash.c" + "${MOCK_GEN_DIR}/Mockesp_flash.c") + + if(${target} STREQUAL "linux") + list(APPEND include_dirs + "${CMAKE_CURRENT_SOURCE_DIR}/../spi_flash/sim/stubs/soc/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../spi_flash/sim/stubs/xtensa") endif() - set(cache_srcs "") - set(priv_requires bootloader_support soc) + + idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ${include_dirs} + REQUIRES cmock) + + # This command builds the mocks. + # First, environment variable UNITY_DIR is set. This is necessary to prevent unity from looking in its own submodule + # which doesn't work in our CI yet... + # The rest is a straight forward call to cmock.rb, consult cmock's documentation for more information. + add_custom_command( + OUTPUT ${MOCK_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env "UNITY_DIR=${IDF_PATH}/components/unity/unity" + ruby + ${CMOCK_DIR}/lib/cmock.rb + -o${CMAKE_CURRENT_SOURCE_DIR}/mock/mock_config.yaml + ${MOCK_HEADERS} + ) + else() - set(cache_srcs - "cache_utils.c" - "flash_mmap.c" - "flash_ops.c" - "${IDF_TARGET}/flash_ops_${IDF_TARGET}.c" - ) - set(srcs - "partition.c") + if(BOOTLOADER_BUILD) + if(CONFIG_IDF_TARGET_ESP32) + # ESP32 Bootloader needs SPIUnlock from this file, but doesn't + # need other parts of this component + set(srcs "esp32/spi_flash_rom_patch.c") + elseif(CONFIG_IDF_TARGET_ESP32S2) + set(srcs "esp32s2/spi_flash_rom_patch.c") + elseif(CONFIG_IDF_TARGET_ESP32S3) + set(srcs "esp32s3/spi_flash_rom_patch.c") + else() + # but on other platforms no source files are needed for bootloader + set(srcs) + endif() + set(cache_srcs "") + set(priv_requires bootloader_support soc) + else() + set(cache_srcs + "cache_utils.c" + "flash_mmap.c" + "flash_ops.c" + "${IDF_TARGET}/flash_ops_${IDF_TARGET}.c" + ) + set(srcs + "partition.c") - if(CONFIG_IDF_TARGET_ESP32) + if(CONFIG_IDF_TARGET_ESP32) + list(APPEND srcs + "esp32/spi_flash_rom_patch.c") + elseif(CONFIG_IDF_TARGET_ESP32S2) + list(APPEND srcs + "esp32s2/spi_flash_rom_patch.c") + elseif(CONFIG_IDF_TARGET_ESP32S3) + list(APPEND srcs + "esp32s3/spi_flash_rom_patch.c") + endif() + + # New implementation after IDF v4.0 list(APPEND srcs - "esp32/spi_flash_rom_patch.c") - elseif(CONFIG_IDF_TARGET_ESP32S2) - list(APPEND srcs - "esp32s2/spi_flash_rom_patch.c") - elseif(CONFIG_IDF_TARGET_ESP32S3) - list(APPEND srcs - "esp32s3/spi_flash_rom_patch.c") + "spi_flash_chip_drivers.c" + "spi_flash_chip_generic.c" + "spi_flash_chip_issi.c" + "spi_flash_chip_mxic.c" + "spi_flash_chip_gd.c" + "spi_flash_chip_winbond.c" + "memspi_host_driver.c") + + list(APPEND cache_srcs + "esp_flash_api.c" + "esp_flash_spi_init.c" + "spi_flash_os_func_app.c" + "spi_flash_os_func_noos.c") + + list(APPEND srcs ${cache_srcs}) + set(priv_requires bootloader_support app_update soc esp_ipc) endif() - # New implementation after IDF v4.0 - list(APPEND srcs - "spi_flash_chip_drivers.c" - "spi_flash_chip_generic.c" - "spi_flash_chip_issi.c" - "spi_flash_chip_mxic.c" - "spi_flash_chip_gd.c" - "spi_flash_chip_winbond.c" - "memspi_host_driver.c") + idf_component_register(SRCS "${srcs}" + REQUIRES hal + PRIV_REQUIRES "${priv_requires}" + INCLUDE_DIRS include + PRIV_INCLUDE_DIRS private_include + LDFRAGMENTS linker.lf) - list(APPEND cache_srcs - "esp_flash_api.c" - "esp_flash_spi_init.c" - "spi_flash_os_func_app.c" - "spi_flash_os_func_noos.c") + # Avoid cache miss by unexpected inlineing when built by -Os + set_source_files_properties(${cache_srcs} PROPERTIES COMPILE_FLAGS + "-fno-inline-functions -fno-inline-small-functions -fno-inline-functions-called-once") - list(APPEND srcs ${cache_srcs}) - set(priv_requires bootloader_support app_update soc esp_ipc) endif() - -idf_component_register(SRCS "${srcs}" - REQUIRES hal - PRIV_REQUIRES "${priv_requires}" - INCLUDE_DIRS include - PRIV_INCLUDE_DIRS private_include - LDFRAGMENTS linker.lf) - -# Avoid cache miss by unexpected inlineing when built by -Os -set_source_files_properties(${cache_srcs} PROPERTIES COMPILE_FLAGS - "-fno-inline-functions -fno-inline-small-functions -fno-inline-functions-called-once") diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index fcc702444f..5e7b77de8a 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -20,6 +20,7 @@ #include #include "esp_err.h" #include "sdkconfig.h" +#include "esp_spi_flash_counters.h" #ifdef __cplusplus extern "C" { @@ -419,43 +420,6 @@ extern const spi_flash_guard_funcs_t g_flash_guard_default_ops; */ extern const spi_flash_guard_funcs_t g_flash_guard_no_os_ops; -#if CONFIG_SPI_FLASH_ENABLE_COUNTERS - -/** - * Structure holding statistics for one type of operation - */ -typedef struct { - uint32_t count; // number of times operation was executed - uint32_t time; // total time taken, in microseconds - uint32_t bytes; // total number of bytes -} spi_flash_counter_t; - -typedef struct { - spi_flash_counter_t read; - spi_flash_counter_t write; - spi_flash_counter_t erase; -} spi_flash_counters_t; - -/** - * @brief Reset SPI flash operation counters - */ -void spi_flash_reset_counters(void); - -/** - * @brief Print SPI flash operation counters - */ -void spi_flash_dump_counters(void); - -/** - * @brief Return current SPI flash operation counters - * - * @return pointer to the spi_flash_counters_t structure holding values - * of the operation counters - */ -const spi_flash_counters_t* spi_flash_get_counters(void); - -#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS - #ifdef __cplusplus } #endif diff --git a/components/spi_flash/include/esp_spi_flash_counters.h b/components/spi_flash/include/esp_spi_flash_counters.h new file mode 100644 index 0000000000..5ec3b95140 --- /dev/null +++ b/components/spi_flash/include/esp_spi_flash_counters.h @@ -0,0 +1,66 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include "esp_err.h" +#include "sdkconfig.h" + +#if CONFIG_SPI_FLASH_ENABLE_COUNTERS + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Structure holding statistics for one type of operation + */ +typedef struct { + uint32_t count; // number of times operation was executed + uint32_t time; // total time taken, in microseconds + uint32_t bytes; // total number of bytes +} spi_flash_counter_t; + +typedef struct { + spi_flash_counter_t read; + spi_flash_counter_t write; + spi_flash_counter_t erase; +} spi_flash_counters_t; + +/** + * @brief Reset SPI flash operation counters + */ +void spi_flash_reset_counters(void); + +/** + * @brief Print SPI flash operation counters + */ +void spi_flash_dump_counters(void); + +/** + * @brief Return current SPI flash operation counters + * + * @return pointer to the spi_flash_counters_t structure holding values + * of the operation counters + */ +const spi_flash_counters_t* spi_flash_get_counters(void); + +#ifdef __cplusplus +} +#endif + +#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS + diff --git a/components/spi_flash/mock/mock_config.yaml b/components/spi_flash/mock/mock_config.yaml new file mode 100644 index 0000000000..60dd3b5267 --- /dev/null +++ b/components/spi_flash/mock/mock_config.yaml @@ -0,0 +1,7 @@ + :cmock: + :plugins: + - expect + - expect_any_args + - return_thru_ptr + - array + - callback diff --git a/components/spi_flash/sim/stubs/esp_common/esp_err.h b/components/spi_flash/sim/stubs/esp_common/esp_err.h new file mode 100644 index 0000000000..105723976d --- /dev/null +++ b/components/spi_flash/sim/stubs/esp_common/esp_err.h @@ -0,0 +1,149 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int32_t esp_err_t; + +/* Definitions for error constants. */ +#define ESP_OK 0 /*!< esp_err_t value indicating success (no error) */ +#define ESP_FAIL -1 /*!< Generic esp_err_t code indicating failure */ + +#define ESP_ERR_NO_MEM 0x101 /*!< Out of memory */ +#define ESP_ERR_INVALID_ARG 0x102 /*!< Invalid argument */ +#define ESP_ERR_INVALID_STATE 0x103 /*!< Invalid state */ +#define ESP_ERR_INVALID_SIZE 0x104 /*!< Invalid size */ +#define ESP_ERR_NOT_FOUND 0x105 /*!< Requested resource not found */ +#define ESP_ERR_NOT_SUPPORTED 0x106 /*!< Operation or feature not supported */ +#define ESP_ERR_TIMEOUT 0x107 /*!< Operation timed out */ +#define ESP_ERR_INVALID_RESPONSE 0x108 /*!< Received response was invalid */ +#define ESP_ERR_INVALID_CRC 0x109 /*!< CRC or checksum was invalid */ +#define ESP_ERR_INVALID_VERSION 0x10A /*!< Version was invalid */ +#define ESP_ERR_INVALID_MAC 0x10B /*!< MAC address was invalid */ + +#define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */ +#define ESP_ERR_MESH_BASE 0x4000 /*!< Starting number of MESH error codes */ +#define ESP_ERR_FLASH_BASE 0x6000 /*!< Starting number of flash error codes */ + +/** + * @brief Returns string for esp_err_t error codes + * + * This function finds the error code in a pre-generated lookup-table and + * returns its string representation. + * + * The function is generated by the Python script + * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t + * error is modified, created or removed from the IDF project. + * + * @param code esp_err_t error code + * @return string error message + */ +const char *esp_err_to_name(esp_err_t code); + +/** + * @brief Returns string for esp_err_t and system error codes + * + * This function finds the error code in a pre-generated lookup-table of + * esp_err_t errors and returns its string representation. If the error code + * is not found then it is attempted to be found among system errors. + * + * The function is generated by the Python script + * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t + * error is modified, created or removed from the IDF project. + * + * @param code esp_err_t error code + * @param[out] buf buffer where the error message should be written + * @param buflen Size of buffer buf. At most buflen bytes are written into the buf buffer (including the terminating null byte). + * @return buf containing the string error message + */ +const char *esp_err_to_name_r(esp_err_t code, char *buf, size_t buflen); + +/** @cond */ +void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression) __attribute__((noreturn)); + +/** @cond */ +void _esp_error_check_failed_without_abort(esp_err_t rc, const char *file, int line, const char *function, const char *expression); + +#ifndef __ASSERT_FUNC +/* This won't happen on IDF, which defines __ASSERT_FUNC in assert.h, but it does happen when building on the host which + uses /usr/include/assert.h or equivalent. +*/ +#ifdef __ASSERT_FUNCTION +#define __ASSERT_FUNC __ASSERT_FUNCTION /* used in glibc assert.h */ +#else +#define __ASSERT_FUNC "??" +#endif +#endif +/** @endcond */ + +/** + * Macro which can be used to check the error code, + * and terminate the program in case the code is not ESP_OK. + * Prints the error code, error location, and the failed statement to serial output. + * + * Disabled if assertions are disabled. + */ +#ifdef NDEBUG +#define ESP_ERROR_CHECK(x) do { \ + esp_err_t __err_rc = (x); \ + (void) sizeof(__err_rc); \ + } while(0) +#elif defined(CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT) +#define ESP_ERROR_CHECK(x) do { \ + esp_err_t __err_rc = (x); \ + if (__err_rc != ESP_OK) { \ + abort(); \ + } \ + } while(0) +#else +#define ESP_ERROR_CHECK(x) do { \ + esp_err_t __err_rc = (x); \ + if (__err_rc != ESP_OK) { \ + _esp_error_check_failed(__err_rc, __FILE__, __LINE__, \ + __ASSERT_FUNC, #x); \ + } \ + } while(0) +#endif + +/** + * Macro which can be used to check the error code. Prints the error code, error location, and the failed statement to + * serial output. + * In comparison with ESP_ERROR_CHECK(), this prints the same error message but isn't terminating the program. + */ +#ifdef NDEBUG +#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({ \ + esp_err_t __err_rc = (x); \ + __err_rc; \ + }) +#else +#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({ \ + esp_err_t __err_rc = (x); \ + if (__err_rc != ESP_OK) { \ + _esp_error_check_failed_without_abort(__err_rc, __FILE__, __LINE__, \ + __ASSERT_FUNC, #x); \ + } \ + __err_rc; \ + }) +#endif //NDEBUG + +#ifdef __cplusplus +} +#endif diff --git a/components/spi_flash/sim/stubs/soc/include/hal/spi_flash_types.h b/components/spi_flash/sim/stubs/soc/include/hal/spi_flash_types.h new file mode 100644 index 0000000000..71cdf73d67 --- /dev/null +++ b/components/spi_flash/sim/stubs/soc/include/hal/spi_flash_types.h @@ -0,0 +1,148 @@ +// Copyright 2010-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** Definition of a common transaction. Also holds the return value. */ +typedef struct { + uint8_t command; ///< Command to send, always 8bits + uint8_t mosi_len; ///< Output data length, in bytes + uint8_t miso_len; ///< Input data length, in bytes + uint8_t address_bitlen; ///< Length of address in bits, set to 0 if command does not need an address + uint32_t address; ///< Address to perform operation on + const uint8_t *mosi_data; ///< Output data to salve + uint8_t *miso_data; ///< [out] Input data from slave, little endian +} spi_flash_trans_t; + +/** @brief Mode used for reading from SPI flash */ +typedef enum { + SPI_FLASH_SLOWRD = 0, ///< Data read using single I/O, some limits on speed + SPI_FLASH_FASTRD, ///< Data read using single I/O, no limit on speed + SPI_FLASH_DOUT, ///< Data read using dual I/O + SPI_FLASH_DIO, ///< Both address & data transferred using dual I/O + SPI_FLASH_QOUT, ///< Data read using quad I/O + SPI_FLASH_QIO, ///< Both address & data transferred using quad I/O + + SPI_FLASH_READ_MODE_MAX, ///< The fastest io mode supported by the host is ``ESP_FLASH_READ_MODE_MAX-1``. +} esp_flash_io_mode_t; + +struct spi_flash_host_driver_s; +typedef struct spi_flash_host_driver_s spi_flash_host_driver_t; + +/** SPI Flash Host driver instance */ +typedef struct { + const struct spi_flash_host_driver_s* driver; ///< Pointer to the implementation function table + // Implementations can wrap this structure into their own ones, and append other data here +} spi_flash_host_inst_t ; + +/** Host driver configuration and context structure. */ +struct spi_flash_host_driver_s { + /** + * Configure the device-related register before transactions. This saves + * some time to re-configure those registers when we send continuously + */ + esp_err_t (*dev_config)(spi_flash_host_inst_t *host); + /** + * Send an user-defined spi transaction to the device. + */ + esp_err_t (*common_command)(spi_flash_host_inst_t *host, spi_flash_trans_t *t); + /** + * Read flash ID. + */ + esp_err_t (*read_id)(spi_flash_host_inst_t *host, uint32_t *id); + /** + * Erase whole flash chip. + */ + void (*erase_chip)(spi_flash_host_inst_t *host); + /** + * Erase a specific sector by its start address. + */ + void (*erase_sector)(spi_flash_host_inst_t *host, uint32_t start_address); + /** + * Erase a specific block by its start address. + */ + void (*erase_block)(spi_flash_host_inst_t *host, uint32_t start_address); + /** + * Read the status of the flash chip. + */ + esp_err_t (*read_status)(spi_flash_host_inst_t *host, uint8_t *out_sr); + /** + * Disable write protection. + */ + esp_err_t (*set_write_protect)(spi_flash_host_inst_t *host, bool wp); + /** + * Program a page of the flash. Check ``max_write_bytes`` for the maximum allowed writing length. + */ + void (*program_page)(spi_flash_host_inst_t *host, const void *buffer, uint32_t address, uint32_t length); + /** Check whether given buffer can be directly used to write */ + bool (*supports_direct_write)(spi_flash_host_inst_t *host, const void *p); + /** + * Slicer for write data. The `program_page` should be called iteratively with the return value + * of this function. + * + * @param address Beginning flash address to write + * @param len Length request to write + * @param align_addr Output of the aligned address to write to + * @param page_size Physical page size of the flash chip + * @return Length that can be actually written in one `program_page` call + */ + int (*write_data_slicer)(spi_flash_host_inst_t *host, uint32_t address, uint32_t len, uint32_t *align_addr, + uint32_t page_size); + /** + * Read data from the flash. Check ``max_read_bytes`` for the maximum allowed reading length. + */ + esp_err_t (*read)(spi_flash_host_inst_t *host, void *buffer, uint32_t address, uint32_t read_len); + /** Check whether given buffer can be directly used to read */ + bool (*supports_direct_read)(spi_flash_host_inst_t *host, const void *p); + /** + * Slicer for read data. The `read` should be called iteratively with the return value + * of this function. + * + * @param address Beginning flash address to read + * @param len Length request to read + * @param align_addr Output of the aligned address to read + * @param page_size Physical page size of the flash chip + * @return Length that can be actually read in one `read` call + */ + int (*read_data_slicer)(spi_flash_host_inst_t *host, uint32_t address, uint32_t len, uint32_t *align_addr, uint32_t page_size); + /** + * Check whether the host is idle to perform new operations. + */ + bool (*host_idle)(spi_flash_host_inst_t *host); + /** + * Configure the host to work at different read mode. Responsible to compensate the timing and set IO mode. + */ + esp_err_t (*configure_host_io_mode)(spi_flash_host_inst_t *host, uint32_t command, + uint32_t addr_bitlen, int dummy_bitlen_base, + esp_flash_io_mode_t io_mode); + /** + * Internal use, poll the HW until the last operation is done. + */ + void (*poll_cmd_done)(spi_flash_host_inst_t *host); + /** + * For some host (SPI1), they are shared with a cache. When the data is + * modified, the cache needs to be flushed. Left NULL if not supported. + */ + esp_err_t (*flush_cache)(spi_flash_host_inst_t* host, uint32_t addr, uint32_t size); +}; +///Slowest io mode supported by ESP32, currently SlowRd +#define SPI_FLASH_READ_MODE_MIN SPI_FLASH_SLOWRD + +#ifdef __cplusplus +} +#endif diff --git a/components/unity/CMakeLists.txt b/components/unity/CMakeLists.txt index 71a9bef47f..b54ffa20c8 100644 --- a/components/unity/CMakeLists.txt +++ b/components/unity/CMakeLists.txt @@ -27,7 +27,7 @@ else() endif() idf_component_register(SRCS "${srcs}" - INCLUDE_DIRS "include" "unity/src" "unity/extras/fixture/src") + INCLUDE_DIRS ${includes}) if(NOT ${IDF_TARGET} STREQUAL "linux") target_compile_definitions(${COMPONENT_LIB} PUBLIC diff --git a/tools/gen_esp_err_to_name.py b/tools/gen_esp_err_to_name.py index 3cfa1e0c43..e88b14cd2c 100755 --- a/tools/gen_esp_err_to_name.py +++ b/tools/gen_esp_err_to_name.py @@ -46,7 +46,9 @@ ignore_files = [os.path.join('components', 'mdns', 'test_afl_fuzz_host', 'esp32_ ] # add directories here which should not be parsed, this is a tuple since it will be used with *.startswith() -ignore_dirs = (os.path.join('examples'), os.path.join('components', 'cmock', 'CMock', 'test')) +ignore_dirs = (os.path.join('examples'), + os.path.join('components', 'cmock', 'CMock', 'test'), + os.path.join('components', 'spi_flash', 'sim')) # macros from here have higher priorities in case of collisions priority_headers = [os.path.join('components', 'esp_common', 'include', 'esp_err.h')] From 00819a302266bdff69482eb60ef94d8f5f2cdba9 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Fri, 6 Nov 2020 15:54:51 +0800 Subject: [PATCH 4/5] NVS flash: host-based unit test of nvs::Page * General tests like page loading from flash * Rough test of fixed-size data types * Rough test of blob read * Added coverage target in cmake, also accessible via `idf.py coverage` * Fixed unsigned comparison in comp. enum table * introducing temporary LINUX_TARGET define --- components/nvs_flash/CMakeLists.txt | 41 +- .../host_test/fixtures/test_fixtures.hpp | 429 ++++++++ .../host_test/nvs_page_test/CMakeLists.txt | 27 + .../host_test/nvs_page_test/README.rst | 23 + .../nvs_page_test/main/CMakeLists.txt | 10 + .../nvs_page_test/main/nvs_page_test.cpp | 934 ++++++++++++++++++ .../nvs_page_test/sdkconfig.defaults | 3 + .../{test_nvs_host => mock/int}/crc.cpp | 2 +- .../{test_nvs_host => mock/int}/crc.h | 6 +- .../nvs_flash/src/compressed_enum_table.hpp | 4 +- components/nvs_flash/src/nvs_api.cpp | 20 +- components/nvs_flash/src/nvs_page.cpp | 18 +- components/nvs_flash/src/nvs_page.hpp | 3 + components/nvs_flash/src/nvs_partition.cpp | 4 +- components/nvs_flash/src/nvs_platform.hpp | 35 +- components/nvs_flash/src/nvs_storage.cpp | 22 +- components/nvs_flash/src/nvs_types.cpp | 20 +- components/nvs_flash/test_nvs_host/Makefile | 8 +- components/spi_flash/CMakeLists.txt | 12 +- .../include/esp_spi_flash_counters.h | 1 - 20 files changed, 1547 insertions(+), 75 deletions(-) create mode 100644 components/nvs_flash/host_test/fixtures/test_fixtures.hpp create mode 100644 components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt create mode 100644 components/nvs_flash/host_test/nvs_page_test/README.rst create mode 100644 components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt create mode 100644 components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp create mode 100644 components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults rename components/nvs_flash/{test_nvs_host => mock/int}/crc.cpp (97%) rename components/nvs_flash/{test_nvs_host => mock/int}/crc.h (72%) diff --git a/components/nvs_flash/CMakeLists.txt b/components/nvs_flash/CMakeLists.txt index 41fe7d8006..7ce2871c1a 100644 --- a/components/nvs_flash/CMakeLists.txt +++ b/components/nvs_flash/CMakeLists.txt @@ -1,3 +1,5 @@ +idf_build_get_property(target IDF_TARGET) + set(srcs "src/nvs_api.cpp" "src/nvs_cxx_api.cpp" "src/nvs_item_hash_list.cpp" @@ -11,10 +13,39 @@ set(srcs "src/nvs_api.cpp" "src/nvs_partition_manager.cpp" "src/nvs_types.cpp") -if(CONFIG_NVS_ENCRYPTION) - list(APPEND srcs "src/nvs_encrypted_partition.cpp") -endif() +set(public_req spi_flash) + +set(include_dirs "include") idf_component_register(SRCS "${srcs}" - REQUIRES spi_flash mbedtls - INCLUDE_DIRS include) + REQUIRES "${public_req}" + INCLUDE_DIRS "${include_dirs}") + +# If we use the linux target, we need to redirect the crc functions to the linux +if(${target} STREQUAL "linux") + if(CONFIG_NVS_ENCRYPTION) + # mbedtls isn't configured for building with linux or as mock target. It will draw in all kind of dependencies + message(FATAL_ERROR "NVS currently doesn't support encryption if built for Linux.") + endif() + idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR) + target_include_directories(${COMPONENT_LIB} PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/mock/int" + "${spi_flash_dir}/sim/stubs/freertos/include") + target_sources(${COMPONENT_LIB} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/mock/int/crc.cpp") + target_compile_options(${COMPONENT_LIB} PUBLIC "-DLINUX_TARGET") +else() + # TODO: this is a workaround until IDF-2085 is fixed + idf_component_get_property(mbedtls_lib mbedtls COMPONENT_LIB) + target_link_libraries(${COMPONENT_LIB} PUBLIC ${mbedtls_lib}) +endif() + +if(CONFIG_NVS_ENCRYPTION) + target_sources(${COMPONENT_LIB} PRIVATE "src/nvs_encrypted_partition.cpp") + idf_component_get_property(mbedtls_lib mbedtls COMPONENT_LIB) + target_link_libraries(${COMPONENT_LIB} PUBLIC ${mbedtls_lib}) +endif() + +if(${target} STREQUAL "linux") + target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) + target_link_libraries(${COMPONENT_LIB} PUBLIC --coverage) +endif() diff --git a/components/nvs_flash/host_test/fixtures/test_fixtures.hpp b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp new file mode 100644 index 0000000000..06b12ce176 --- /dev/null +++ b/components/nvs_flash/host_test/fixtures/test_fixtures.hpp @@ -0,0 +1,429 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nvs_partition.hpp" +#include "nvs.h" +#include "nvs_page.hpp" +#include "nvs_storage.hpp" +#include +#include + +#ifdef CONFIG_NVS_ENCRYPTION +#include "nvs_encrypted_partition.hpp" +#endif + +extern "C" { +#include "Mockesp_partition.h" +} + +struct FixtureException : std::exception { + FixtureException(const std::string& msg) : msg(msg) { } + + const char *what() { + return msg.c_str(); + } + + std::string msg; +}; + +class PartitionMock : public nvs::Partition { +public: + PartitionMock(uint32_t address, uint32_t size) + : partition(), address(address), size(size) + { + assert(size); + } + + const char *get_partition_name() override + { + return ""; + } + + esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override + { + return esp_partition_read_raw(&partition, src_offset, dst, size); + } + + esp_err_t read(size_t src_offset, void* dst, size_t size) override + { + return esp_partition_read(&partition, src_offset, dst, size); + } + + esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override + { + return esp_partition_write_raw(&partition, dst_offset, src, size); + } + + esp_err_t write(size_t dst_offset, const void* src, size_t size) override + { + return esp_partition_write(&partition, dst_offset, src, size); + } + + esp_err_t erase_range(size_t dst_offset, size_t size) override + { + return esp_partition_erase_range(&partition, dst_offset, size); + } + + uint32_t get_address() override + { + return address; + } + + uint32_t get_size() override + { + return size; + } + + const esp_partition_t partition; + +private: + uint32_t address; + + uint32_t size; +}; + +#ifdef CONFIG_NVS_ENCRYPTION +struct EncryptedPartitionFixture { + EncryptedPartitionFixture(nvs_sec_cfg_t *cfg, + uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : esp_partition(), emu(start_sector + sector_size), + part(partition_name, &esp_partition) { + esp_partition.address = start_sector * SPI_FLASH_SEC_SIZE; + esp_partition.size = sector_size * SPI_FLASH_SEC_SIZE; + assert(part.init(cfg) == ESP_OK); + } + + ~EncryptedPartitionFixture() { } + + esp_partition_t esp_partition; + + SpiFlashEmulator emu; + + nvs::NVSEncryptedPartition part; +}; +#endif + +struct PartitionMockFixture { + PartitionMockFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : part_mock(start_sector * SPI_FLASH_SEC_SIZE, sector_size * SPI_FLASH_SEC_SIZE) { + std::fill_n(raw_header, sizeof(raw_header)/sizeof(raw_header[0]), UINT8_MAX); + } + + ~PartitionMockFixture() { } + + uint8_t raw_header[512]; + + PartitionMock part_mock; +}; + +struct NVSPageFixture : public PartitionMockFixture { + NVSPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), page() + { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 32); + + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512); + } + + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Page page; +}; + +struct NVSValidPageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + + // valid header + uint8_t raw_header_valid [32]; + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t ns_entry [32]; + + uint8_t value_entry [32]; + + NVSValidPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), + raw_header_valid {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc}, + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + value_entry {0x01, 0x01, 0x01, 0xff, 0x3d, 0xf3, 0x99, 0xe5, 't', 'e', 's', 't', '_', 'v', 'a', 'l', + 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + page() + { + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0xfa; + + // read page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // read next free entry's header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 4); + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32); + + // read normal entry second time during duplicated entry check + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32); + + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Page page; +}; + +struct NVSValidStorageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + + uint8_t ns_entry [32]; + + uint8_t empty_entry [32]; + + NVSValidStorageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 3, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + empty_entry(), + storage(&part_mock) + { + std::fill_n(empty_entry, sizeof(empty_entry)/sizeof(empty_entry[0]), 0xFF); + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t header_full_page [] = { + 0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc}; + + uint8_t header_second_page [] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + uint8_t header_third_page [] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + // entry_table with all elements deleted except the namespace entry written and the last entry free + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0x02; + raw_entry_table[31] = 0xFC; + + // read full page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header_full_page, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // reading entry table checks empty entry + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(empty_entry, 32); + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read last two pages' headers, which trigger an automatic full read each because each page is empty + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header_second_page, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512); + } + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header_third_page, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512); + } + + // read namespace entry in duplicated header item check of pagemanager::load + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // storage finally actually reads namespace + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // storage looks for blob index entries + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // Storage::eraseOrphanDataBlobs() also wants to take it's turn... + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + if (storage.init(start_sector, sector_size) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Storage storage; +}; + +struct NVSValidBlobPageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + const static size_t BLOB_DATA_SIZE = 32; + + // valid header + uint8_t raw_header_valid [32]; + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t ns_entry [32]; + + uint8_t blob_entry [32]; + uint8_t blob_data [BLOB_DATA_SIZE]; + uint8_t blob_index [32]; + + NVSValidBlobPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME) + : PartitionMockFixture(start_sector, sector_size, partition_name), + raw_header_valid {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc}, + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + blob_entry {0x01, 0x42, 0x02, 0x00, 0xaa, 0xf3, 0x23, 0x87, 't', 'e', 's', 't', '_', 'b', 'l', 'o', + 'b', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 0x20, 0x00, 0xff, 0xff, 0xc6, 0x96, 0x86, 0xd9}, + blob_data {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}, + blob_index {0x01, 0x48, 0x01, 0xff, 0x42, 0x6b, 0xdf, 0x66, 't', 'e', 's', 't', '_', 'b', 'l', 'o', + 'b', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff}, + page() + { + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0xFF); + raw_entry_table[0] = 0xaa; + + // read page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // read next free entry's header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 4); + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read normal blob entry + index, not the data + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(blob_index, 32); + + // read normal entry second time during duplicated entry check + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(blob_entry, 32); + + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + + nvs::Page page; +}; + +struct NVSFullPageFixture : public PartitionMockFixture { + const static uint8_t NS_INDEX = 1; + + // valid header + uint8_t raw_header_valid [32]; + + // entry table with one entry + uint8_t raw_entry_table [32]; + + uint8_t ns_entry [32]; + + uint8_t value_entry [32]; + + NVSFullPageFixture(uint32_t start_sector = 0, + uint32_t sector_size = 1, + const char *partition_name = NVS_DEFAULT_PART_NAME, + bool load = true) + : PartitionMockFixture(start_sector, sector_size, partition_name), + raw_header_valid {0xfc, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa3, 0x48, 0x9f, 0x38}, + ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + value_entry {0x01, 0x01, 0x01, 0xff, 0x3d, 0xf3, 0x99, 0xe5, 't', 'e', 's', 't', '_', 'v', 'a', 'l', + 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + page() + { + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0xfa; + + // entry_table with all elements deleted except the namespace entry written and the last entry free + std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0); + raw_entry_table[0] = 0x0a; + raw_entry_table[31] = 0xFC; + + // read page header + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32); + + // read entry table + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32); + + // no next free entry check, only one entry written + + // read namespace entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32); + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32); + + // no duplicated entry check + + if (load) { + if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page"); + } + } + + nvs::Page page; +}; diff --git a/components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt b/components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt new file mode 100644 index 0000000000..cafc7c6999 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +idf_build_set_property(CONFIG_SPI_FLASH_MOCK 1) +idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND) +project(host_nvs_page_test) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + COMMAND lcov --capture --directory . --output-file coverage.info + COMMENT "Create coverage report" + ) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + COMMAND genhtml coverage.info --output-directory coverage_report/ + COMMENT "Turn coverage report into html-based visualization" + ) + +add_custom_target(coverage + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + DEPENDS "coverage_report/" + ) diff --git a/components/nvs_flash/host_test/nvs_page_test/README.rst b/components/nvs_flash/host_test/nvs_page_test/README.rst new file mode 100644 index 0000000000..c005b3f48d --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/README.rst @@ -0,0 +1,23 @@ +NVS Page Test for Host +====================== + +Build +----- + +First, make sure that the target is set to linux. +Run ``idf.py --preview set-target linux`` to be sure. +Then do a normal IDF build: ``idf.py build``. + +Run +--- + +IDF monitor doesn't work yet for Linux. +You have to run the app manually: ``./build/host_nvs_page_test.elf``. + +Coverage +--- + +To generate the coverage, run: ``idf.py coverage``. +Afterwards, you can view the coverage by opening ``build/coverage_report/index.html`` with your browser. +Note that you need to run the application at least once before generating the coverage information. +If you run it multiple times, the coverage information adds up. diff --git a/components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt b/components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt new file mode 100644 index 0000000000..d2af1b6a6c --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register(SRCS "nvs_page_test.cpp" + INCLUDE_DIRS + "." + "${CMAKE_CURRENT_SOURCE_DIR}/../../fixtures" + "${CMAKE_CURRENT_SOURCE_DIR}/../../../test_nvs_host" + "${CMAKE_CURRENT_SOURCE_DIR}/../../../src" + REQUIRES cmock nvs_flash spi_flash) + +target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) +target_link_libraries(${COMPONENT_LIB} --coverage) diff --git a/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp b/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp new file mode 100644 index 0000000000..d3899be783 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp @@ -0,0 +1,934 @@ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "unity.h" +#include "test_fixtures.hpp" + +extern "C" { +#include "Mockesp_partition.h" +} + +using namespace std; +using namespace nvs; + +void test_Page_load_reading_header_fails() +{ + PartitionMock mock(0, 4096); + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_ARG); + Page page; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, page.load(&mock, 0)); +} + +void test_Page_load_reading_data_fails() +{ + uint8_t header[64]; + std::fill_n(header, sizeof(header)/sizeof(header[0]), UINT8_MAX); + PartitionMock mock(0, 4096); + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(header, 32); + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_FAIL); + Page page; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + TEST_ASSERT_EQUAL(ESP_FAIL, page.load(&mock, 0)); +} + +void test_Page_load__uninitialized_page_has_0xfe() +{ + PartitionMockFixture fix; + Page page; + + fix.raw_header[511] = 0xfe; + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32); + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512); + + // Page::load() should return ESP_OK, but state has to be corrupt + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + + TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); +} + +void test_Page_load__initialized_corrupt_header() +{ + PartitionMockFixture fix; + Page page; + + uint8_t raw_header_corrupt [] = {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x16, 0xdd, 0xdc}; + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_corrupt, 32); + + // Page::load() should return ESP_OK, but state has to be corrupt + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + + TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); +} + +void test_Page_load_success() +{ + PartitionMockFixture fix; + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512); + } + Page page; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, page.state()); +} + +void test_Page_load_full_page() +{ + NVSFullPageFixture fix(0, 1, NVS_DEFAULT_PART_NAME, false); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); + TEST_ASSERT_EQUAL(ESP_OK, fix.page.load(&fix.part_mock, 0)); + TEST_ASSERT_EQUAL(Page::PageState::FULL, fix.page.state()); +} +void test_Page_load__seq_number_0() +{ + NVSValidPageFixture fix; + + uint32_t seq_num; + fix.page.getSeqNumber(seq_num); + TEST_ASSERT_EQUAL(0, seq_num); +} + +void test_Page_erase__write_fail() +{ + NVSValidPageFixture fix; + + esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 0, 4096, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.erase()); + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_erase__success() +{ + NVSValidPageFixture fix; + + esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 0, 4096, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.erase()); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); +} + +void test_Page_write__initialize_write_failure() +{ + PartitionMockFixture fix; + uint8_t write_data = 47; + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32); + for (int i = 0; i < 8; i++) { + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512); + } + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_FAIL); + + Page page; + + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, page.state()); + + TEST_ASSERT_EQUAL(ESP_FAIL, page.writeItem(1, nvs::ItemType::U8, "test", &write_data, sizeof(write_data))); + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); +} + +void test_Page_write__write_data_fails() +{ + NVSPageFixture fix; + uint8_t write_data = 47; + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_write_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.writeItem(1, nvs::ItemType::U8, "test", &write_data, sizeof(write_data))); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_page_write__write_correct_entry_state() +{ + NVSPageFixture fix; + uint8_t write_data = 47; + uint8_t raw_result [4]; + std::fill_n(raw_result, sizeof(raw_result)/sizeof(raw_result[0]), UINT8_MAX); + // mark first entry as written + raw_result[0] = 0xfe; + + // initialize page + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + + // write entry + esp_partition_write_ExpectAnyArgsAndReturn(ESP_OK); + + // write entry state + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.writeItem(1, nvs::ItemType::U8, "test_key", &write_data, sizeof(write_data))); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_write__write_correct_data() +{ + NVSPageFixture fix; + uint8_t write_data = 47; + uint8_t raw_result [32] = {0x01, 0x01, 0x01, 0xff, 0x98, 0x6f, 0x21, 0xfd, 't', 'e', 's', 't', '_', 'k', 'e', 'y', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + // initialize page + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + + // write entry + esp_partition_write_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 64, raw_result, 32, 32, ESP_OK); + + // write entry state + esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK); + + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.writeItem(1, nvs::ItemType::U8, "test_key", &write_data, sizeof(write_data))); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__read_entry_fails() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t read_value = 0; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); +} + +void test_Page_readItem__read_corrupted_entry() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + uint8_t read_value = 0; + + // corrupting entry + fix.value_entry[0] = 0x0; + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount()); +} + +void test_Page_readItem__read_corrupted_second_read_fail() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + uint8_t read_value = 0; + + // corrupting entry + fix.value_entry[0] = 0x0; + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_readItem__read_corrupted_erase_fail() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + uint8_t read_value = 0; + + // corrupting entry + fix.value_entry[0] = 0x0; + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_readItem__read_entry_suceeds() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t read_value = 0; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value)); + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(47, read_value); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__blob_read_data_fails() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__corrupt_data_erase_failure() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + fix.blob_data[16] = 0xdf; + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__blob_corrupt_data() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + fix.blob_data[16] = 0xdf; + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xa2, 0xff, 0xff, 0xff}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 96, 64, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL(3, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(1, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_readItem__blob_read_entry_suceeds() +{ + NVSValidBlobPageFixture fix; + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + uint8_t chunk_start = 0; + uint8_t read_data [32]; + + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.readItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + read_data, + 32, + chunk_start)); + + TEST_ASSERT_EQUAL_MEMORY(fix.blob_data, read_data, fix.BLOB_DATA_SIZE); + + // make sure nothing was erased, i.e. checksums matched + TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_cmp__uninitialized() +{ + Page page; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, page.cmpItem(uint8_t(1) , "test", 47)); +} + +void test_Page_cmp__item_not_found() +{ + NVSValidPageFixture fix; + + // no expectations here since comparison uses the item hash list + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.cmpItem(uint8_t(1), "different", 47)); +} + +void test_Page_cmp__item_type_mismatch() +{ + NVSValidPageFixture fix; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_TYPE_MISMATCH, fix.page.cmpItem(uint8_t(1), "test_value", int(47))); +} + +void test_Page_cmp__item_content_mismatch() +{ + NVSValidPageFixture fix; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_CONTENT_DIFFERS, fix.page.cmpItem(uint8_t(1), "test_value", uint8_t(46))); +} + +void test_Page_cmp__item_content_match() +{ + NVSValidPageFixture fix; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.cmpItem(NVSValidPageFixture::NS_INDEX, "test_value", uint8_t(47))); +} + +void test_Page_cmpItem__blob_data_mismatch() +{ + NVSValidBlobPageFixture fix; + + // read blob entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + + // read blob data + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + + uint8_t blob_data_different [] = + {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xee}; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_CONTENT_DIFFERS, + fix.page.cmpItem(uint8_t(1), + ItemType::BLOB_DATA, + "test_blob", + blob_data_different, + 32)); +} + +void test_Page_cmpItem__blob_data_match() +{ + NVSValidBlobPageFixture fix; + + // read blob entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32); + + // read blob data + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE); + + + uint8_t blob_data_same [] = + {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}; + + TEST_ASSERT_EQUAL(ESP_OK, + fix.page.cmpItem(NVSValidPageFixture::NS_INDEX, + ItemType::BLOB_DATA, + "test_blob", + blob_data_same, + 32)); +} + +void test_Page_eraseItem__uninitialized() +{ + Page page; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, page.eraseItem(NVSValidPageFixture::NS_INDEX, "test_value")); +} + +void test_Page_eraseItem__key_not_found() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.eraseItem(NVSValidPageFixture::NS_INDEX, "different")); + + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_eraseItem__write_fail() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.eraseItem(NVSValidPageFixture::NS_INDEX, "test_value")); + + TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_eraseItem__write_succeed() +{ + NVSValidPageFixture fix; + TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); + + // first read the entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // Page::eraseEntryAndSpan() reads entry again + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + // erasing entry by setting bit in entry table (0xfa -> 0xf2) + uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00}; + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.eraseItem(NVSValidPageFixture::NS_INDEX, "test_value")); + + TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount()); + TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount()); + + TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state()); +} + +void test_Page_findItem__uninitialized() +{ + Page page; + + size_t index = 0; + Item item; + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "test_value", index, item)); +} + +void test_Page_find__wrong_ns() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX + 1, nvs::ItemType::U8, "test_value", index, item)); +} + +void test_Page_find__wrong_type() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + // read normal entry + esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32); + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_TYPE_MISMATCH, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::I8, "test_value", index, item)); +} + +void test_Page_find__key_empty() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "", index, item)); +} + +void test_Page_find__wrong_key() +{ + NVSValidPageFixture fix; + size_t index = 0; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "different", index, item)); +} + +void test_Page_find__too_large_index() +{ + NVSValidPageFixture fix; + size_t index = 2; + Item item; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "test_value", index, item)); +} + +void test_Page_findItem__without_read() +{ + NVSValidPageFixture fix; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, + fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "different")); +} + +void test_Page_markFull__wrong_state() +{ + NVSPageFixture fix; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, fix.page.markFull()); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); +} + +void test_Page_markFull__success() +{ + NVSValidPageFixture fix; + Page::PageState expected_state = Page::PageState::FULL; + + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.markFull()); + TEST_ASSERT_EQUAL(Page::PageState::FULL, fix.page.state()); +} + +void test_Page_markFull__write_fail() +{ + NVSValidPageFixture fix; + Page::PageState expected_state = Page::PageState::FREEING; + + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_FAIL); + + TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.markFreeing()); + TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state()); +} + +void test_Page_markFreeing__wrong_state() +{ + NVSPageFixture fix; + + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, fix.page.markFreeing()); + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); +} + +void test_Page_markFreeing__success() +{ + NVSValidPageFixture fix; + Page::PageState expected_state = Page::PageState::FREEING; + + esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_OK); + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.markFreeing()); + TEST_ASSERT_EQUAL(Page::PageState::FREEING, fix.page.state()); +} + +void test_Page_getVarDataTailroom__uninitialized_page() +{ + NVSPageFixture fix; + + TEST_ASSERT_EQUAL(Page::CHUNK_MAX_SIZE, fix.page.getVarDataTailroom()); +} + +void test_Page_getVarDataTailroom__success() +{ + NVSValidPageFixture fix; + + // blob data item, written namespace item, written normal item: 3 items + TEST_ASSERT_EQUAL((Page::ENTRY_COUNT - 3) * Page::ENTRY_SIZE, fix.page.getVarDataTailroom()); +} + +void test_Page_calcEntries__uninit() +{ + NVSPageFixture fix; + TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state()); + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(0, nvsStats.used_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__corrupt() +{ + PartitionMockFixture fix; + Page page; + + uint8_t raw_header_corrupt [] = {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x16, 0xdd, 0xdc}; + + esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK); + esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_corrupt, 32); + + // Page::load() should return ESP_OK, but state has to be corrupt + TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0)); + + TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state()); + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(0, nvsStats.used_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__active_wo_blob() +{ + NVSValidPageFixture fix; + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(2, nvsStats.used_entries); + TEST_ASSERT_EQUAL(124, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__active_with_blob() +{ + NVSValidBlobPageFixture fix; + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(4, nvsStats.used_entries); + TEST_ASSERT_EQUAL(122, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +void test_Page_calcEntries__invalid() +{ + Page page; + + nvs_stats_t nvsStats = {0, 0, 0, 0}; + + TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state()); + + // total entries always get updated + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, page.calcEntries(nvsStats)); + TEST_ASSERT_EQUAL(0, nvsStats.used_entries); + TEST_ASSERT_EQUAL(0, nvsStats.free_entries); + TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries); + TEST_ASSERT_EQUAL(0, nvsStats.namespace_count); +} + +int main(int argc, char **argv) +{ + UNITY_BEGIN(); + RUN_TEST(test_Page_load_reading_header_fails); + RUN_TEST(test_Page_load_reading_data_fails); + RUN_TEST(test_Page_load__uninitialized_page_has_0xfe); + RUN_TEST(test_Page_load__initialized_corrupt_header); + RUN_TEST(test_Page_load_success); + RUN_TEST(test_Page_load_full_page); + RUN_TEST(test_Page_load__seq_number_0); + RUN_TEST(test_Page_erase__write_fail); + RUN_TEST(test_Page_erase__success); + RUN_TEST(test_Page_write__initialize_write_failure); + RUN_TEST(test_Page_write__write_data_fails); + RUN_TEST(test_page_write__write_correct_entry_state); + RUN_TEST(test_Page_write__write_correct_data); + RUN_TEST(test_Page_readItem__read_entry_fails); + RUN_TEST(test_Page_readItem__read_corrupted_entry); + RUN_TEST(test_Page_readItem__read_corrupted_second_read_fail); + RUN_TEST(test_Page_readItem__read_corrupted_erase_fail); + RUN_TEST(test_Page_readItem__read_entry_suceeds); + RUN_TEST(test_Page_readItem__blob_read_data_fails); + RUN_TEST(test_Page_readItem__blob_corrupt_data); + RUN_TEST(test_Page_readItem__blob_read_entry_suceeds); + RUN_TEST(test_Page_cmp__uninitialized); + RUN_TEST(test_Page_cmp__item_not_found); + RUN_TEST(test_Page_cmp__item_type_mismatch); + RUN_TEST(test_Page_cmp__item_content_mismatch); + RUN_TEST(test_Page_cmp__item_content_match); + RUN_TEST(test_Page_cmpItem__blob_data_mismatch); + RUN_TEST(test_Page_cmpItem__blob_data_match); + RUN_TEST(test_Page_eraseItem__uninitialized); + RUN_TEST(test_Page_eraseItem__key_not_found); + RUN_TEST(test_Page_eraseItem__write_fail); + RUN_TEST(test_Page_readItem__corrupt_data_erase_failure); + RUN_TEST(test_Page_eraseItem__write_succeed); + RUN_TEST(test_Page_findItem__uninitialized); + RUN_TEST(test_Page_find__wrong_ns); + RUN_TEST(test_Page_find__wrong_type); + RUN_TEST(test_Page_find__key_empty); + RUN_TEST(test_Page_find__wrong_key); + RUN_TEST(test_Page_find__too_large_index); + RUN_TEST(test_Page_findItem__without_read); + RUN_TEST(test_Page_markFull__wrong_state); + RUN_TEST(test_Page_markFreeing__wrong_state); + RUN_TEST(test_Page_markFull__success); + RUN_TEST(test_Page_markFreeing__success); + RUN_TEST(test_Page_markFull__write_fail); + RUN_TEST(test_Page_getVarDataTailroom__uninitialized_page); + RUN_TEST(test_Page_getVarDataTailroom__success); + RUN_TEST(test_Page_calcEntries__uninit); + RUN_TEST(test_Page_calcEntries__corrupt); + RUN_TEST(test_Page_calcEntries__active_wo_blob); + RUN_TEST(test_Page_calcEntries__active_with_blob); + RUN_TEST(test_Page_calcEntries__invalid); + UNITY_END(); + return 0; +} diff --git a/components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults b/components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults new file mode 100644 index 0000000000..a057733348 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n +CONFIG_IDF_TARGET="linux" +CONFIG_CXX_EXCEPTIONS=y diff --git a/components/nvs_flash/test_nvs_host/crc.cpp b/components/nvs_flash/mock/int/crc.cpp similarity index 97% rename from components/nvs_flash/test_nvs_host/crc.cpp rename to components/nvs_flash/mock/int/crc.cpp index 49980776c3..5c1b745648 100644 --- a/components/nvs_flash/test_nvs_host/crc.cpp +++ b/components/nvs_flash/mock/int/crc.cpp @@ -52,7 +52,7 @@ static const unsigned int crc32_le_table[256] = { -extern "C" unsigned int crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len) +extern "C" uint32_t esp_rom_crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len) { unsigned int i; crc = ~crc; diff --git a/components/nvs_flash/test_nvs_host/crc.h b/components/nvs_flash/mock/int/crc.h similarity index 72% rename from components/nvs_flash/test_nvs_host/crc.h rename to components/nvs_flash/mock/int/crc.h index c752b30303..f675da3271 100644 --- a/components/nvs_flash/test_nvs_host/crc.h +++ b/components/nvs_flash/mock/int/crc.h @@ -20,7 +20,11 @@ extern "C" { #endif -uint32_t crc32_le(uint32_t crc, const uint8_t* buf, size_t len); +/** + * Mock function to replace ESP ROM function used in IDF with a Linux implementation. + * Note: the name MUST have the prefix esp_rom_* since tools/ci/check_rom_apis.sh checks and complains otherwise. + */ +uint32_t esp_rom_crc32_le(uint32_t crc, const uint8_t* buf, size_t len); #ifdef __cplusplus } diff --git a/components/nvs_flash/src/compressed_enum_table.hpp b/components/nvs_flash/src/compressed_enum_table.hpp index 319d86a45c..dcf9d09993 100644 --- a/components/nvs_flash/src/compressed_enum_table.hpp +++ b/components/nvs_flash/src/compressed_enum_table.hpp @@ -35,7 +35,7 @@ public: Tenum get(size_t index) const { - assert(index >= 0 && index < Nitems); + assert(index < Nitems); size_t wordIndex = index / ITEMS_PER_WORD; size_t offset = (index % ITEMS_PER_WORD) * Nbits; @@ -44,7 +44,7 @@ public: void set(size_t index, Tenum val) { - assert(index >= 0 && index < Nitems); + assert(index < Nitems); size_t wordIndex = index / ITEMS_PER_WORD; size_t offset = (index % ITEMS_PER_WORD) * Nbits; diff --git a/components/nvs_flash/src/nvs_api.cpp b/components/nvs_flash/src/nvs_api.cpp index 423e1fdceb..008ff463eb 100644 --- a/components/nvs_flash/src/nvs_api.cpp +++ b/components/nvs_flash/src/nvs_api.cpp @@ -22,17 +22,17 @@ #include #include "nvs_handle_simple.hpp" -#ifdef ESP_PLATFORM +#ifdef LINUX_TARGET +#include "crc.h" +#define ESP_LOGD(...) +#else // LINUX_TARGET #include // Uncomment this line to force output from this module // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #include "esp_log.h" static const char* TAG = "nvs"; -#else -#include "crc.h" -#define ESP_LOGD(...) -#endif +#endif // ! LINUX_TARGET class NVSHandleEntry : public intrusive_list_node { public: @@ -56,9 +56,9 @@ uint32_t NVSHandleEntry::s_nvs_next_handle; extern "C" void nvs_dump(const char *partName); -#ifdef ESP_PLATFORM +#ifndef LINUX_TARGET SemaphoreHandle_t nvs::Lock::mSemaphore = nullptr; -#endif +#endif // ! LINUX_TARGET using namespace std; using namespace nvs; @@ -125,7 +125,7 @@ extern "C" esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partiti return init_res; } -#ifdef ESP_PLATFORM +#ifndef LINUX_TARGET extern "C" esp_err_t nvs_flash_init_partition(const char *part_name) { Lock::init(); @@ -204,7 +204,7 @@ extern "C" esp_err_t nvs_flash_erase(void) { return nvs_flash_erase_partition(NVS_DEFAULT_PART_NAME); } -#endif // ESP_PLATFORM +#endif // ! LINUX_TARGET extern "C" esp_err_t nvs_flash_deinit_partition(const char* partition_name) { @@ -528,7 +528,7 @@ extern "C" esp_err_t nvs_get_used_entry_count(nvs_handle_t c_handle, size_t* use return err; } -#if (defined CONFIG_NVS_ENCRYPTION) && (defined ESP_PLATFORM) +#if (defined CONFIG_NVS_ENCRYPTION) && (!defined LINUX_TARGET) extern "C" esp_err_t nvs_flash_generate_keys(const esp_partition_t* partition, nvs_sec_cfg_t* cfg) { diff --git a/components/nvs_flash/src/nvs_page.cpp b/components/nvs_flash/src/nvs_page.cpp index 0563eca2b8..2f72a2090d 100644 --- a/components/nvs_flash/src/nvs_page.cpp +++ b/components/nvs_flash/src/nvs_page.cpp @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. #include "nvs_page.hpp" -#if defined(ESP_PLATFORM) -#include -#else +#if defined(LINUX_TARGET) #include "crc.h" +#else +#include #endif #include #include @@ -27,7 +27,7 @@ Page::Page() : mPartition(nullptr) { } uint32_t Page::Header::calculateCrc32() { - return crc32_le(0xffffffff, + return esp_rom_crc32_le(0xffffffff, reinterpret_cast(this) + offsetof(Header, mSeqNumber), offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber)); } @@ -137,7 +137,7 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size) const uint8_t* buf = data; -#ifdef ESP_PLATFORM +#if !defined LINUX_TARGET // TODO: check whether still necessary with esp_partition* API /* On the ESP32, data can come from DROM, which is not accessible by spi_flash_write * function. To work around this, we copy the data to heap if it came from DROM. @@ -153,15 +153,15 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size) } memcpy((void*)buf, data, size); } -#endif //ESP_PLATFORM +#endif // ! LINUX_TARGET auto rc = mPartition->write(getEntryAddress(mNextFreeEntry), buf, size); -#ifdef ESP_PLATFORM +#if !defined LINUX_TARGET if (buf != data) { free((void*)buf); } -#endif //ESP_PLATFORM +#endif // ! LINUX_TARGET if (rc != ESP_OK) { mState = PageState::INVALID; return rc; @@ -968,7 +968,7 @@ size_t Page::getVarDataTailroom() const } else if (mState == PageState::FULL) { return 0; } - /* Skip one entry for header*/ + /* Skip one entry for blob data item precessing the data */ return ((mNextFreeEntry < (ENTRY_COUNT-1)) ? ((ENTRY_COUNT - mNextFreeEntry - 1) * ENTRY_SIZE): 0); } diff --git a/components/nvs_flash/src/nvs_page.hpp b/components/nvs_flash/src/nvs_page.hpp index bb6a0dca62..5857f1ffe4 100644 --- a/components/nvs_flash/src/nvs_page.hpp +++ b/components/nvs_flash/src/nvs_page.hpp @@ -224,6 +224,9 @@ protected: uint16_t mUsedEntryCount = 0; uint16_t mErasedEntryCount = 0; + /** + * This hash list stores hashes of namespace index, key, and ChunkIndex for quick lookup when searching items. + */ HashList mHashList; Partition *mPartition; diff --git a/components/nvs_flash/src/nvs_partition.cpp b/components/nvs_flash/src/nvs_partition.cpp index 370316af08..f1b7d36d2d 100644 --- a/components/nvs_flash/src/nvs_partition.cpp +++ b/components/nvs_flash/src/nvs_partition.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "string.h" +#include #include "nvs_partition.hpp" namespace nvs { @@ -22,7 +22,7 @@ NVSPartition::NVSPartition(const esp_partition_t* partition) { // ensure the class is in a valid state if (partition == nullptr) { - abort(); + std::abort(); } } diff --git a/components/nvs_flash/src/nvs_platform.hpp b/components/nvs_flash/src/nvs_platform.hpp index 0973c4875c..5c6b5b8b23 100644 --- a/components/nvs_flash/src/nvs_platform.hpp +++ b/components/nvs_flash/src/nvs_platform.hpp @@ -11,11 +11,23 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#ifndef nvs_platform_h -#define nvs_platform_h +#pragma once +#ifdef LINUX_TARGET +namespace nvs +{ +class Lock +{ +public: + Lock() { } + ~Lock() { } + static void init() {} + static void uninit() {} +}; +} // namespace nvs + +#else // LINUX_TARGET -#ifdef ESP_PLATFORM #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -63,19 +75,4 @@ public: }; } // namespace nvs -#else // ESP_PLATFORM -namespace nvs -{ -class Lock -{ -public: - Lock() { } - ~Lock() { } - static void init() {} - static void uninit() {} -}; -} // namespace nvs -#endif // ESP_PLATFORM - - -#endif /* nvs_platform_h */ +#endif // LINUX_TARGET diff --git a/components/nvs_flash/src/nvs_storage.cpp b/components/nvs_flash/src/nvs_storage.cpp index 88176c1045..52952b7a38 100644 --- a/components/nvs_flash/src/nvs_storage.cpp +++ b/components/nvs_flash/src/nvs_storage.cpp @@ -14,9 +14,15 @@ #include "nvs_storage.hpp" #ifndef ESP_PLATFORM +// We need NO_DEBUG_STORAGE here since the integration tests on the host add some debug code. +// The unit tests, however, don't want debug code since they check the behavior via data in/output and disturb +// the order of calling mocked functions. +#ifndef NO_DEBUG_STORAGE #include #include +#define DEBUG_STORAGE #endif +#endif // !ESP_PLATFORM namespace nvs { @@ -136,7 +142,7 @@ esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount) // Purge the blob index list blobIdxList.clearAndFreeNodes(); -#ifndef ESP_PLATFORM +#ifdef DEBUG_STORAGE debugCheck(); #endif return ESP_OK; @@ -165,7 +171,7 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo uint8_t chunkCount = 0; TUsedPageList usedPages; size_t remainingSize = dataSize; - size_t offset=0; + size_t offset = 0; esp_err_t err = ESP_OK; /* Check how much maximum data can be accommodated**/ @@ -182,8 +188,8 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo do { Page& page = getCurrentPage(); size_t tailroom = page.getVarDataTailroom(); - size_t chunkSize =0; - if (!chunkCount && tailroom < dataSize && tailroom < Page::CHUNK_MAX_SIZE/10) { + size_t chunkSize = 0; + if (chunkCount == 0U && ((tailroom < dataSize) || (tailroom == 0 && dataSize == 0)) && tailroom < Page::CHUNK_MAX_SIZE/10) { /** This is the first chunk and tailroom is too small ***/ if (page.state() != Page::PageState::FULL) { err = page.markFull(); @@ -206,7 +212,7 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo } /* Split the blob into two and store the chunk of available size onto the current page */ - assert(tailroom!=0); + assert(tailroom != 0); chunkSize = (remainingSize > tailroom)? tailroom : remainingSize; remainingSize -= chunkSize; @@ -383,7 +389,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key return err; } } -#ifndef ESP_PLATFORM +#ifdef DEBUG_STORAGE debugCheck(); #endif return ESP_OK; @@ -666,7 +672,7 @@ void Storage::debugDump() } } -#ifndef ESP_PLATFORM +#ifdef DEBUG_STORAGE void Storage::debugCheck() { std::map keys; @@ -691,7 +697,7 @@ void Storage::debugCheck() assert(usedCount == p->getUsedEntryCount()); } } -#endif //ESP_PLATFORM +#endif //DEBUG_STORAGE esp_err_t Storage::fillStats(nvs_stats_t& nvsStats) { diff --git a/components/nvs_flash/src/nvs_types.cpp b/components/nvs_flash/src/nvs_types.cpp index f6b4f31e7f..0189dd70b9 100644 --- a/components/nvs_flash/src/nvs_types.cpp +++ b/components/nvs_flash/src/nvs_types.cpp @@ -13,10 +13,10 @@ // limitations under the License. #include "nvs_types.hpp" -#if defined(ESP_PLATFORM) -#include -#else +#if defined(LINUX_TARGET) #include "crc.h" +#else +#include #endif namespace nvs @@ -25,10 +25,10 @@ uint32_t Item::calculateCrc32() const { uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); - result = crc32_le(result, p + offsetof(Item, nsIndex), + result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), offsetof(Item, crc32) - offsetof(Item, nsIndex)); - result = crc32_le(result, p + offsetof(Item, key), sizeof(key)); - result = crc32_le(result, p + offsetof(Item, data), sizeof(data)); + result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); + result = esp_rom_crc32_le(result, p + offsetof(Item, data), sizeof(data)); return result; } @@ -36,17 +36,17 @@ uint32_t Item::calculateCrc32WithoutValue() const { uint32_t result = 0xffffffff; const uint8_t* p = reinterpret_cast(this); - result = crc32_le(result, p + offsetof(Item, nsIndex), + result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex), offsetof(Item, datatype) - offsetof(Item, nsIndex)); - result = crc32_le(result, p + offsetof(Item, key), sizeof(key)); - result = crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex)); + result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key)); + result = esp_rom_crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex)); return result; } uint32_t Item::calculateCrc32(const uint8_t* data, size_t size) { uint32_t result = 0xffffffff; - result = crc32_le(result, data, size); + result = esp_rom_crc32_le(result, data, size); return result; } diff --git a/components/nvs_flash/test_nvs_host/Makefile b/components/nvs_flash/test_nvs_host/Makefile index c04ae3a424..32964fa3e6 100644 --- a/components/nvs_flash/test_nvs_host/Makefile +++ b/components/nvs_flash/test_nvs_host/Makefile @@ -3,6 +3,7 @@ all: $(TEST_PROGRAM) SOURCE_FILES = \ esp_error_check_stub.cpp \ + ../mock/int/crc.cpp \ $(addprefix ../src/, \ nvs_types.cpp \ nvs_api.cpp \ @@ -28,7 +29,6 @@ SOURCE_FILES = \ test_nvs_partition.cpp \ test_nvs_cxx_api.cpp \ test_nvs_initialization.cpp \ - crc.cpp \ main.cpp ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1) @@ -37,9 +37,9 @@ else COMPILER := gcc endif -CPPFLAGS += -I../include -I../src -I./ -I../../esp_common/include -I../../esp32/include -I ../../mbedtls/mbedtls/include -I ../../spi_flash/include -I ../../hal/include -I ../../xtensa/include -I ../../../tools/catch -fprofile-arcs -ftest-coverage -g2 -ggdb -CFLAGS += -fprofile-arcs -ftest-coverage -CXXFLAGS += -std=c++11 -Wall -Werror +CPPFLAGS += -I../include -I../src -I../mock/int -I./ -I../../esp_common/include -I../../esp32/include -I ../../mbedtls/mbedtls/include -I ../../spi_flash/include -I ../../hal/include -I ../../xtensa/include -I ../../../tools/catch -fprofile-arcs -ftest-coverage -g2 -ggdb +CFLAGS += -fprofile-arcs -ftest-coverage -DLINUX_TARGET +CXXFLAGS += -std=c++11 -Wall -Werror -DLINUX_TARGET LDFLAGS += -lstdc++ -Wall -fprofile-arcs -ftest-coverage ifeq ($(COMPILER),clang) diff --git a/components/spi_flash/CMakeLists.txt b/components/spi_flash/CMakeLists.txt index ab0aef765f..df7b630c16 100644 --- a/components/spi_flash/CMakeLists.txt +++ b/components/spi_flash/CMakeLists.txt @@ -1,7 +1,6 @@ idf_build_get_property(spi_flash_mock CONFIG_SPI_FLASH_MOCK) idf_build_get_property(target IDF_TARGET) if(${spi_flash_mock}) - message(STATUS "building SPI FLASH MOCKS") set(IDF_PATH $ENV{IDF_PATH}) @@ -41,13 +40,20 @@ if(${spi_flash_mock}) INCLUDE_DIRS ${include_dirs} REQUIRES cmock) + add_custom_command( + OUTPUT ruby_found SYMBOLIC + COMMAND "ruby" "-v" + COMMENT "Try to find ruby. If this fails, you need to install ruby" + ) + # This command builds the mocks. # First, environment variable UNITY_DIR is set. This is necessary to prevent unity from looking in its own submodule # which doesn't work in our CI yet... # The rest is a straight forward call to cmock.rb, consult cmock's documentation for more information. add_custom_command( - OUTPUT ${MOCK_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env "UNITY_DIR=${IDF_PATH}/components/unity/unity" + OUTPUT ${MOCK_OUTPUT} + DEPENDS ruby_found + COMMAND ${CMAKE_COMMAND} -E env "UNITY_DIR=${IDF_PATH}/components/unity/unity" ruby ${CMOCK_DIR}/lib/cmock.rb -o${CMAKE_CURRENT_SOURCE_DIR}/mock/mock_config.yaml diff --git a/components/spi_flash/include/esp_spi_flash_counters.h b/components/spi_flash/include/esp_spi_flash_counters.h index 5ec3b95140..ab8157c256 100644 --- a/components/spi_flash/include/esp_spi_flash_counters.h +++ b/components/spi_flash/include/esp_spi_flash_counters.h @@ -63,4 +63,3 @@ const spi_flash_counters_t* spi_flash_get_counters(void); #endif #endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS - From fcabbe5f3340f9a79ab54def5b1b554f381da706 Mon Sep 17 00:00:00 2001 From: Fu Hanxi Date: Fri, 27 Nov 2020 18:06:36 +0800 Subject: [PATCH 5/5] Add Linux support for find_apps and build_apps --- tools/find_build_apps/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/find_build_apps/common.py b/tools/find_build_apps/common.py index 56fa696f05..12fd9f000d 100644 --- a/tools/find_build_apps/common.py +++ b/tools/find_build_apps/common.py @@ -289,12 +289,13 @@ class BuildSystem: Objects of these classes aren't instantiated, instead the class (type object) is used. """ NAME = "undefined" - SUPPORTED_TARGETS_REGEX = re.compile(r'Supported [Tt]argets((?:[\s|]+(?:ESP[0-9A-Z\-]+))+)') + SUPPORTED_TARGETS_REGEX = re.compile(r'Supported [Tt]argets((?:[ |]+(?:[0-9a-zA-Z\-]+))+)') FORMAL_TO_USUAL = { 'ESP32': 'esp32', 'ESP32-S2': 'esp32s2', 'ESP32-S3': 'esp32s3', + 'Linux': 'linux', } @classmethod