[zephyr] Allow to override default malloc with sys_heap (#16926)
* [zephyr] Allow to override default malloc with sys_heap
Add a setting that overrides default malloc/calloc/realloc/
free functions with custom implementations based on sys_heap
from Zephyr RTOS.
Update DiagnosticDataProvider methods for obtaining the heap
statistics to use the sys_heap statistics when sys_heap-baed
malloc is used. Additionally, fix the DiagnosticDataProvider
implementation using mallinfo() by taking the maximum heap
size into account.
* Restyled by whitespace
* Restyled by clang-format
* Restyled by gn
* Handle mul overflow
* Add separate switch for wrapping malloc/calloc/realloc/free symbols
* Restyled by gn
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/config/nrfconnect/chip-module/CMakeLists.txt b/config/nrfconnect/chip-module/CMakeLists.txt
index bdc434f..ede0541 100644
--- a/config/nrfconnect/chip-module/CMakeLists.txt
+++ b/config/nrfconnect/chip-module/CMakeLists.txt
@@ -218,6 +218,7 @@
chip_gn_arg_bool ("chip_progress_logging" CONFIG_MATTER_LOG_LEVEL GREATER_EQUAL 3)
chip_gn_arg_bool ("chip_detail_logging" CONFIG_MATTER_LOG_LEVEL GREATER_EQUAL 4)
chip_gn_arg_bool ("chip_automation_logging" "false")
+chip_gn_arg_bool ("chip_malloc_sys_heap" CONFIG_CHIP_MALLOC_SYS_HEAP)
if (CONFIG_CHIP_ROTATING_DEVICE_ID)
chip_gn_arg_bool("chip_enable_rotating_device_id" "true")
@@ -305,6 +306,19 @@
target_link_options(chip INTERFACE -Wl,--whole-archive -lCHIPShell -Wl,--no-whole-archive)
endif()
+if (CONFIG_CHIP_MALLOC_SYS_HEAP_OVERRIDE)
+ target_link_options(chip INTERFACE
+ -Wl,--wrap=malloc
+ -Wl,--wrap=calloc
+ -Wl,--wrap=realloc
+ -Wl,--wrap=free
+ -Wl,--wrap=_malloc_r
+ -Wl,--wrap=_calloc_r
+ -Wl,--wrap=_realloc_r
+ -Wl,--wrap=_free_r
+ )
+endif()
+
target_link_libraries(chip INTERFACE -Wl,--start-group ${CHIP_LIBRARIES} -Wl,--end-group)
add_dependencies(chip chip-gn)
diff --git a/config/zephyr/Kconfig b/config/zephyr/Kconfig
index 8152bd4..8d88467 100644
--- a/config/zephyr/Kconfig
+++ b/config/zephyr/Kconfig
@@ -175,6 +175,33 @@
precisely operation time in case of device reboot and maximizing flash memory
lifetime.
+config CHIP_MALLOC_SYS_HEAP
+ bool "Memory allocator based on Zephyr sys_heap"
+ imply SYS_HEAP_RUNTIME_STATS
+ help
+ Enable memory allocation functions, imitating with the default malloc,
+ calloc, realloc and free, based on sys_heap from Zephyr RTOS.
+
+if CHIP_MALLOC_SYS_HEAP
+
+config CHIP_MALLOC_SYS_HEAP_OVERRIDE
+ bool "Override default allocator with custom one based on Zephyr sys_heap"
+ default y
+ help
+ Replace the default memory allocation functions, such as malloc, calloc,
+ realloc, free and their reentrant versions, with their counterparts based
+ on sys_heap from Zephyr RTOS.
+
+config CHIP_MALLOC_SYS_HEAP_SIZE
+ int "Heap size used by memory allocator based on Zephyr sys_heap"
+ default 16384 # 16kB
+ help
+ This value controls how much of the device RAM is reserved for the heap
+ used by the memory allocation functions based on sys_heap from Zephyr
+ RTOS.
+
+endif
+
config APP_LINK_WITH_CHIP
bool "Link 'app' with Connected Home over IP"
default y
diff --git a/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp b/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp
index 8d16ac3..c357892 100644
--- a/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp
+++ b/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp
@@ -26,8 +26,10 @@
#include <lib/support/logging/CHIPLogging.h>
#include <platform/DiagnosticDataProvider.h>
#include <platform/Zephyr/DiagnosticDataProviderImpl.h>
+#include <platform/Zephyr/SysHeapMalloc.h>
#include <drivers/hwinfo.h>
+#include <sys/util.h>
#ifdef CONFIG_MCUBOOT_IMG_MANAGER
#include <dfu/mcuboot.h>
@@ -35,6 +37,19 @@
#include <malloc.h>
+#if CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+
+#ifdef CONFIG_NEWLIB_LIBC_ALIGNED_HEAP_SIZE
+const size_t kMaxHeapSize = CONFIG_NEWLIB_LIBC_ALIGNED_HEAP_SIZE;
+#elif defined(CONFIG_NEWLIB_LIBC)
+extern char _end[];
+const size_t kMaxHeapSize = CONFIG_SRAM_BASE_ADDRESS + KB(CONFIG_SRAM_SIZE) - POINTER_TO_UINT(_end);
+#else
+#pragma error "Maximum heap size is required but unknown"
+#endif
+
+#endif
+
namespace chip {
namespace DeviceLayer {
@@ -101,11 +116,15 @@
CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapFree(uint64_t & currentHeapFree)
{
-#ifdef CONFIG_NEWLIB_LIBC
- // This will return the amount of memory which has been allocated from the system, but is not
- // used right now. Ideally, this value should be increased by the amount of memory which can
- // be allocated from the system, but Zephyr does not expose that number.
- currentHeapFree = mallinfo().fordblks;
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP
+ Malloc::Stats stats;
+ ReturnErrorOnFailure(Malloc::GetStats(stats));
+
+ currentHeapFree = stats.free;
+ return CHIP_NO_ERROR;
+#elif CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+ const auto stats = mallinfo();
+ currentHeapFree = kMaxHeapSize - stats.arena + stats.fordblks;
return CHIP_NO_ERROR;
#else
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
@@ -114,7 +133,13 @@
CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapUsed(uint64_t & currentHeapUsed)
{
-#ifdef CONFIG_NEWLIB_LIBC
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP
+ Malloc::Stats stats;
+ ReturnErrorOnFailure(Malloc::GetStats(stats));
+
+ currentHeapUsed = stats.used;
+ return CHIP_NO_ERROR;
+#elif CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
currentHeapUsed = mallinfo().uordblks;
return CHIP_NO_ERROR;
#else
@@ -124,7 +149,14 @@
CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark)
{
-#ifdef CONFIG_NEWLIB_LIBC
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP
+ Malloc::Stats stats;
+ ReturnErrorOnFailure(Malloc::GetStats(stats));
+
+ // TODO: use the maximum usage once that is implemented in Zephyr
+ currentHeapHighWatermark = stats.used;
+ return CHIP_NO_ERROR;
+#elif CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
// ARM newlib does not provide a way to obtain the peak heap usage, so for now just return
// the amount of memory allocated from the system which should be an upper bound of the peak
// usage provided that the heap is not very fragmented.
diff --git a/src/platform/Zephyr/SysHeapMalloc.cpp b/src/platform/Zephyr/SysHeapMalloc.cpp
new file mode 100644
index 0000000..68f8d21
--- /dev/null
+++ b/src/platform/Zephyr/SysHeapMalloc.cpp
@@ -0,0 +1,180 @@
+/*
+ *
+ * Copyright (c) 2022 Project CHIP Authors
+ *
+ * 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 "SysHeapMalloc.h"
+
+#include <lib/support/CodeUtils.h>
+#include <system/SystemError.h>
+
+extern "C" {
+#include <init.h>
+#include <sys/math_extras.h>
+#include <sys/mutex.h>
+#include <sys/sys_heap.h>
+}
+
+#include <cstdint>
+#include <cstring>
+
+// Construct name of the given function wrapped with the `--wrap=symbol` GCC option.
+#define WRAP(f) __wrap_##f
+
+using namespace chip;
+
+namespace {
+
+// Alignment of memory blocks returned by malloc.
+// Choose the value that guarantees that the returned memory blocks are castable to all built-in types.
+constexpr size_t kMallocAlignment = alignof(long long);
+
+uint8_t sHeapMemory[CONFIG_CHIP_MALLOC_SYS_HEAP_SIZE] alignas(kMallocAlignment);
+sys_heap sHeap;
+SYS_MUTEX_DEFINE(sLock);
+
+// RAII helper for synchronizing access to the common heap.
+class LockGuard
+{
+public:
+ LockGuard() : mStatus(sys_mutex_lock(&sLock, K_FOREVER)) {}
+ ~LockGuard();
+
+ bool Locked() const { return mStatus == 0; }
+ CHIP_ERROR Error() const { return System::MapErrorZephyr(mStatus); }
+
+private:
+ const int mStatus;
+};
+
+LockGuard::~LockGuard()
+{
+ if (mStatus == 0)
+ {
+ sys_mutex_unlock(&sLock);
+ }
+}
+
+int initHeap(const device *)
+{
+ sys_heap_init(&sHeap, sHeapMemory, sizeof(sHeapMemory));
+ return 0;
+}
+
+} // namespace
+
+// Initialize the heap in the POST_KERNEL phase to make sure that it is ready even before
+// C++ static constructors are called (which happens prior to the APPLICATION initialization phase).
+SYS_INIT(initHeap, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
+
+namespace chip {
+namespace DeviceLayer {
+namespace Malloc {
+
+void * Malloc(size_t size)
+{
+ LockGuard lockGuard;
+
+ return lockGuard.Locked() ? sys_heap_aligned_alloc(&sHeap, kMallocAlignment, size) : nullptr;
+}
+
+void * Calloc(size_t num, size_t size)
+{
+ size_t totalSize;
+
+ if (size_mul_overflow(num, size, &totalSize))
+ {
+ return nullptr;
+ }
+
+ void * mem = malloc(totalSize);
+
+ if (mem)
+ {
+ memset(mem, 0, totalSize);
+ }
+
+ return mem;
+}
+
+void * Realloc(void * mem, size_t size)
+{
+ LockGuard lockGuard;
+
+ return lockGuard.Locked() ? sys_heap_aligned_realloc(&sHeap, mem, kMallocAlignment, size) : nullptr;
+}
+
+void Free(void * mem)
+{
+ LockGuard lockGuard;
+
+ VerifyOrReturn(lockGuard.Locked());
+ sys_heap_free(&sHeap, mem);
+}
+
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+
+CHIP_ERROR GetStats(Stats & stats)
+{
+ LockGuard lockGuard;
+ ReturnErrorOnFailure(lockGuard.Error());
+
+ sys_heap_runtime_stats sysHeapStats;
+ ReturnErrorOnFailure(System::MapErrorZephyr(sys_heap_runtime_stats_get(&sHeap, &sysHeapStats)));
+
+ stats.free = sysHeapStats.free_bytes;
+ stats.used = sysHeapStats.allocated_bytes;
+
+ return CHIP_NO_ERROR;
+}
+
+#endif // CONFIG_SYS_HEAP_RUNTIME_STATS
+
+} // namespace Malloc
+} // namespace DeviceLayer
+} // namespace chip
+
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP_OVERRIDE
+
+extern "C" {
+
+void * WRAP(malloc)(size_t size) __attribute((alias("_ZN4chip11DeviceLayer6Malloc6MallocEj")));
+void * WRAP(calloc)(size_t num, size_t size) __attribute((alias("_ZN4chip11DeviceLayer6Malloc6CallocEjj")));
+void * WRAP(realloc)(void * mem, size_t size) __attribute((alias("_ZN4chip11DeviceLayer6Malloc7ReallocEPvj")));
+void WRAP(free)(void * mem) __attribute((alias("_ZN4chip11DeviceLayer6Malloc4FreeEPv")));
+
+void * WRAP(_malloc_r)(_reent *, size_t size)
+{
+ return WRAP(malloc)(size);
+}
+
+void * WRAP(_calloc_r)(_reent *, size_t num, size_t size)
+{
+ return WRAP(calloc)(num, size);
+}
+
+void * WRAP(_realloc_r)(_reent *, void * mem, size_t size)
+{
+ return WRAP(realloc)(mem, size);
+}
+
+void WRAP(_free_r)(_reent *, void * mem)
+{
+ WRAP(free)(mem);
+}
+
+} // extern "C"
+
+#endif // CONFIG_CHIP_MALLOC_SYS_HEAP_OVERRIDE
diff --git a/src/platform/Zephyr/SysHeapMalloc.h b/src/platform/Zephyr/SysHeapMalloc.h
new file mode 100644
index 0000000..3ee6f50
--- /dev/null
+++ b/src/platform/Zephyr/SysHeapMalloc.h
@@ -0,0 +1,40 @@
+/*
+ *
+ * Copyright (c) 2022 Project CHIP Authors
+ *
+ * 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 <cstddef>
+
+#include <lib/core/CHIPError.h>
+
+namespace chip {
+namespace DeviceLayer {
+namespace Malloc {
+
+struct Stats
+{
+ size_t free;
+ size_t used;
+};
+
+void * Malloc(size_t size);
+void * Calloc(size_t num, size_t size);
+void * Realloc(void * mem, size_t size);
+void Free(void * mem);
+CHIP_ERROR GetStats(Stats & stats);
+
+} // namespace Malloc
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/nrfconnect/BUILD.gn b/src/platform/nrfconnect/BUILD.gn
index bf342e9..b587734 100644
--- a/src/platform/nrfconnect/BUILD.gn
+++ b/src/platform/nrfconnect/BUILD.gn
@@ -15,6 +15,7 @@
import("//build_overrides/chip.gni")
import("${chip_root}/src/platform/device.gni")
+import("${chip_root}/src/platform/nrfconnect/args.gni")
assert(chip_device_platform == "nrfconnect")
@@ -28,6 +29,7 @@
"../Zephyr/KeyValueStoreManagerImpl.cpp",
"../Zephyr/Logging.cpp",
"../Zephyr/PlatformManagerImpl.cpp",
+ "../Zephyr/SysHeapMalloc.h",
"../Zephyr/SystemTimeSupport.cpp",
"../Zephyr/ZephyrConfig.cpp",
"../Zephyr/ZephyrConfig.h",
@@ -77,4 +79,8 @@
"OTAImageProcessorImpl.h",
]
}
+
+ if (chip_malloc_sys_heap) {
+ sources += [ "../Zephyr/SysHeapMalloc.cpp" ]
+ }
}
diff --git a/src/platform/nrfconnect/CHIPDevicePlatformConfig.h b/src/platform/nrfconnect/CHIPDevicePlatformConfig.h
index 3748d3b..c3d87d1 100644
--- a/src/platform/nrfconnect/CHIPDevicePlatformConfig.h
+++ b/src/platform/nrfconnect/CHIPDevicePlatformConfig.h
@@ -73,6 +73,13 @@
#define CHIP_DEVICE_CONFIG_SERVER_SHUTDOWN_ACTIONS_SLEEP_MS 10
#endif // CHIP_DEVICE_CONFIG_SERVER_SHUTDOWN_ACTIONS_SLEEP_MS
+#ifndef CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+#if !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+/// Use mallinfo() to obtain the heap usage statistics exposed by SoftwareDiagnostics cluster attributes.
+#define CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO 1
+#endif // !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+#endif // CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+
// ========== Platform-specific Configuration Overrides =========
#ifndef CHIP_DEVICE_CONFIG_CHIP_TASK_PRIORITY
diff --git a/src/platform/nrfconnect/args.gni b/src/platform/nrfconnect/args.gni
index 06a372e..64e4dcb 100644
--- a/src/platform/nrfconnect/args.gni
+++ b/src/platform/nrfconnect/args.gni
@@ -12,6 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-chip_device_platform = "nrfconnect"
-
-chip_inet_config_enable_ipv4 = false
+declare_args() {
+ chip_malloc_sys_heap = false
+}
diff --git a/src/platform/telink/CHIPDevicePlatformConfig.h b/src/platform/telink/CHIPDevicePlatformConfig.h
index 7498c30..61de429 100644
--- a/src/platform/telink/CHIPDevicePlatformConfig.h
+++ b/src/platform/telink/CHIPDevicePlatformConfig.h
@@ -44,6 +44,13 @@
#define CHIP_DEVICE_CONFIG_SETTINGS_KEY "mt"
#endif // CHIP_DEVICE_CONFIG_SETTINGS_KEY
+#ifndef CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+#if !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+/// Use mallinfo() to obtain the heap usage statistics exposed by SoftwareDiagnostics cluster attributes.
+#define CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO 1
+#endif // !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+#endif // CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+
// ========== Platform-specific Configuration Overrides =========
#ifndef CHIP_DEVICE_CONFIG_CHIP_TASK_PRIORITY