1 /*
2  Tests for the capabilities-based memory allocator.
3 */
4 
5 #include <esp_types.h>
6 #include <stdio.h>
7 #include "unity.h"
8 #include "esp_attr.h"
9 #include "esp_heap_caps.h"
10 #include "esp_spi_flash.h"
11 #include "soc/soc_memory_types.h"
12 #include <stdlib.h>
13 #include <sys/param.h>
14 
15 #ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
16 TEST_CASE("Capabilities allocator test", "[heap]")
17 {
18     char *m1, *m2[10];
19     int x;
20     size_t free8start, free32start, free8, free32;
21 
22     /* It's important we printf() something before we take the empty heap sizes,
23        as the first printf() in a task allocates heap resources... */
24     printf("Testing capabilities allocator...\n");
25 
26     free8start = heap_caps_get_free_size(MALLOC_CAP_8BIT);
27     free32start = heap_caps_get_free_size(MALLOC_CAP_32BIT);
28     printf("Free 8bit-capable memory (start): %dK, 32-bit capable memory %dK\n", free8start, free32start);
29     TEST_ASSERT(free32start >= free8start);
30 
31     printf("Allocating 10K of 8-bit capable RAM\n");
32     m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT);
33     printf("--> %p\n", m1);
34     free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT);
35     free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
36     printf("Free 8bit-capable memory (both reduced): %dK, 32-bit capable memory %dK\n", free8, free32);
37     //Both should have gone down by 10K; 8bit capable ram is also 32-bit capable
38     TEST_ASSERT(free8<=(free8start-10*1024));
39     TEST_ASSERT(free32<=(free32start-10*1024));
40     //Assume we got DRAM back
41     TEST_ASSERT((((int)m1)&0xFF000000)==0x3F000000);
42     free(m1);
43 
44     //The goal here is to allocate from IRAM. Since there is no external IRAM (yet)
45     //the following gives size of IRAM-only (not D/IRAM) memory.
46     size_t free_iram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL) -
47                            heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
48     size_t alloc32 = MIN(free_iram / 2, 10*1024) & (~3);
49     if(free_iram) {
50         printf("Freeing; allocating %u bytes of 32K-capable RAM\n", alloc32);
51         m1 = heap_caps_malloc(alloc32, MALLOC_CAP_32BIT);
52         printf("--> %p\n", m1);
53         //Check that we got IRAM back
54         TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
55         free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT);
56         free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
57         printf("Free 8bit-capable memory (after 32-bit): %dK, 32-bit capable memory %dK\n", free8, free32);
58         //Only 32-bit should have gone down by alloc32: 32-bit isn't necessarily 8bit capable
59         TEST_ASSERT(free32<=(free32start-alloc32));
60         TEST_ASSERT(free8==free8start);
61         free(m1);
62     } else {
63         printf("This platform has no 32-bit only capable RAM, jumping to next test \n");
64     }
65 
66     printf("Allocating impossible caps\n");
67     m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT|MALLOC_CAP_EXEC);
68     printf("--> %p\n", m1);
69     TEST_ASSERT(m1==NULL);
70 
71     if(free_iram) {
72         printf("Testing changeover iram -> dram");
73         // priorities will exhaust IRAM first, then start allocating from DRAM
74         for (x=0; x<10; x++) {
75             m2[x]= heap_caps_malloc(alloc32, MALLOC_CAP_32BIT);
76             printf("--> %p\n", m2[x]);
77         }
78         TEST_ASSERT((((int)m2[0])&0xFF000000)==0x40000000);
79         TEST_ASSERT((((int)m2[9])&0xFF000000)==0x3F000000);
80 
81     } else {
82         printf("This platform has no IRAM-only so changeover will never occur, jumping to next test\n");
83     }
84 
85     printf("Test if allocating executable code still gives IRAM, even with dedicated IRAM region depleted\n");
86     if(free_iram) {
87         // (the allocation should come from D/IRAM)
88         free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC);
89         m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC);
90         printf("--> %p\n", m1);
91         TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
92         for (x=0; x<10; x++) free(m2[x]);
93 
94     } else {
95         // (the allocation should come from D/IRAM)
96         free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC);
97         m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC);
98         printf("--> %p\n", m1);
99         TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
100     }
101 
102     free(m1);
103     printf("Done.\n");
104 }
105 #endif
106 
107 #ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
108 TEST_CASE("IRAM_8BIT capability test", "[heap]")
109 {
110     uint8_t *ptr;
111     size_t free_size, free_size32, largest_free_size;
112 
113     /* need to print something as first printf allocates some heap */
114     printf("IRAM_8BIT capability test\n");
115 
116     free_size = heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT);
117     free_size32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
118 
119     largest_free_size = heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT);
120 
121     ptr = heap_caps_malloc(largest_free_size, MALLOC_CAP_IRAM_8BIT);
122 
123     TEST_ASSERT((((int)ptr)&0xFF000000)==0x40000000);
124 
125     TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT) == (free_size - heap_caps_get_allocated_size(ptr)));
126     TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_32BIT) == (free_size32 - heap_caps_get_allocated_size(ptr)));
127 
128     free(ptr);
129 }
130 #endif
131 
132 TEST_CASE("heap_caps metadata test", "[heap]")
133 {
134     /* need to print something as first printf allocates some heap */
135     printf("heap_caps metadata test\n");
136     heap_caps_print_heap_info(MALLOC_CAP_8BIT);
137 
138     multi_heap_info_t original;
139     heap_caps_get_info(&original, MALLOC_CAP_8BIT);
140 
141     void *b = heap_caps_malloc(original.largest_free_block, MALLOC_CAP_8BIT);
142     TEST_ASSERT_NOT_NULL(b);
143 
144     printf("After allocating %d bytes:\n", original.largest_free_block);
145     heap_caps_print_heap_info(MALLOC_CAP_8BIT);
146 
147     multi_heap_info_t after;
148     heap_caps_get_info(&after, MALLOC_CAP_8BIT);
149     TEST_ASSERT(after.largest_free_block <= original.largest_free_block);
150     TEST_ASSERT(after.total_free_bytes <= original.total_free_bytes);
151 
152     free(b);
153     heap_caps_get_info(&after, MALLOC_CAP_8BIT);
154 
155     printf("\n\n After test, heap status:\n");
156     heap_caps_print_heap_info(MALLOC_CAP_8BIT);
157 
158     /* Allow some leeway here, because LWIP sometimes allocates up to 144 bytes in the background
159        as part of timer management.
160     */
161     TEST_ASSERT_INT32_WITHIN(200, after.total_free_bytes, original.total_free_bytes);
162     TEST_ASSERT_INT32_WITHIN(200, after.largest_free_block, original.largest_free_block);
163     TEST_ASSERT(after.minimum_free_bytes < original.total_free_bytes);
164 }
165 
166 /* Small function runs from IRAM to check that malloc/free/realloc
167    all work OK when cache is disabled...
168 */
iram_malloc_test(void)169 static IRAM_ATTR __attribute__((noinline)) bool iram_malloc_test(void)
170 {
171     spi_flash_guard_get()->start(); // Disables flash cache
172 
173     bool result = true;
174     void *x = heap_caps_malloc(64, MALLOC_CAP_EXEC);
175     result = result && (x != NULL);
176     void *y = heap_caps_realloc(x, 32, MALLOC_CAP_EXEC);
177     result = result && (y != NULL);
178     heap_caps_free(y);
179 
180     spi_flash_guard_get()->end(); // Re-enables flash cache
181 
182     return result;
183 }
184 
185 
186 TEST_CASE("heap_caps_xxx functions work with flash cache disabled", "[heap]")
187 {
188     TEST_ASSERT( iram_malloc_test() );
189 }
190 
191 #ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
192 TEST_CASE("When enabled, allocation operation failure generates an abort", "[heap][reset=abort,SW_CPU_RESET]")
193 {
194     const size_t stupid_allocation_size = (128 * 1024 * 1024);
195     void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
196     (void)ptr;
197     TEST_FAIL_MESSAGE("should not be reached");
198 }
199 #endif
200 
201 static bool called_user_failed_hook = false;
202 
heap_caps_alloc_failed_hook(size_t requested_size,uint32_t caps,const char * function_name)203 void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const char *function_name)
204 {
205     printf("%s was called but failed to allocate %d bytes with 0x%X capabilities. \n",function_name, requested_size, caps);
206     called_user_failed_hook = true;
207 }
208 
209 TEST_CASE("user provided alloc failed hook must be called when allocation fails", "[heap]")
210 {
211     TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
212 
213     const size_t stupid_allocation_size = (128 * 1024 * 1024);
214     void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
215     TEST_ASSERT(called_user_failed_hook != false);
216 
217     called_user_failed_hook = false;
218     ptr = heap_caps_realloc(ptr, stupid_allocation_size, MALLOC_CAP_DEFAULT);
219     TEST_ASSERT(called_user_failed_hook != false);
220 
221     called_user_failed_hook = false;
222     ptr = heap_caps_aligned_alloc(0x200, stupid_allocation_size, MALLOC_CAP_DEFAULT);
223     TEST_ASSERT(called_user_failed_hook != false);
224 
225     (void)ptr;
226 }
227 
228 TEST_CASE("allocation with invalid capability should also trigger the alloc failed hook", "[heap]")
229 {
230     const size_t allocation_size = 64;
231     const uint32_t invalid_cap = MALLOC_CAP_INVALID;
232 
233     TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
234 
235     called_user_failed_hook = false;
236     void *ptr = heap_caps_malloc(allocation_size, invalid_cap);
237     TEST_ASSERT(called_user_failed_hook != false);
238 
239     called_user_failed_hook = false;
240     ptr = heap_caps_realloc(ptr, allocation_size, invalid_cap);
241     TEST_ASSERT(called_user_failed_hook != false);
242 
243     called_user_failed_hook = false;
244     ptr = heap_caps_aligned_alloc(0x200, allocation_size, invalid_cap);
245     TEST_ASSERT(called_user_failed_hook != false);
246 
247     (void)ptr;
248 }
249 
250 #ifdef CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP
251 /**
252  * In MR 16031, the priority of RTC memory has been adjusted to the lowest.
253  * RTC memory will not be consumed a lot during the startup process.
254  */
255 TEST_CASE("RTC memory shoule be lowest priority and its free size should be big enough", "[heap]")
256 {
257     const size_t allocation_size = 1024 * 4;
258     void *ptr = NULL;
259     size_t free_size = 0;
260 
261     ptr = heap_caps_malloc(allocation_size, MALLOC_CAP_DEFAULT);
262     TEST_ASSERT_NOT_NULL(ptr);
263     TEST_ASSERT(!esp_ptr_in_rtc_dram_fast(ptr));
264 
265     free_size = heap_caps_get_free_size(MALLOC_CAP_RTCRAM);
266     TEST_ASSERT_GREATER_OR_EQUAL(1024 * 4, free_size);
267 
268     free(ptr);
269 }
270 #endif
271