/ components / heap / heap_caps.c
heap_caps.c
  1  // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
  2  //
  3  // Licensed under the Apache License, Version 2.0 (the "License");
  4  // you may not use this file except in compliance with the License.
  5  // You may obtain a copy of the License at
  6  
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  9  // Unless required by applicable law or agreed to in writing, software
 10  // distributed under the License is distributed on an "AS IS" BASIS,
 11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  // See the License for the specific language governing permissions and
 13  // limitations under the License.
 14  #include <stdbool.h>
 15  #include <string.h>
 16  #include <assert.h>
 17  #include <stdio.h>
 18  #include <sys/param.h>
 19  #include "esp_attr.h"
 20  #include "esp_heap_caps.h"
 21  #include "multi_heap.h"
 22  #include "esp_log.h"
 23  #include "heap_private.h"
 24  #include "esp_system.h"
 25  
 26  /*
 27  This file, combined with a region allocator that supports multiple heaps, solves the problem that the ESP32 has RAM
 28  that's slightly heterogeneous. Some RAM can be byte-accessed, some allows only 32-bit accesses, some can execute memory,
 29  some can be remapped by the MMU to only be accessed by a certain PID etc. In order to allow the most flexible memory
 30  allocation possible, this code makes it possible to request memory that has certain capabilities. The code will then use
 31  its knowledge of how the memory is configured along with a priority scheme to allocate that memory in the most sane way
 32  possible. This should optimize the amount of RAM accessible to the code without hardwiring addresses.
 33  */
 34  
 35  static esp_alloc_failed_hook_t alloc_failed_callback;
 36  
 37  /*
 38    This takes a memory chunk in a region that can be addressed as both DRAM as well as IRAM. It will convert it to
 39    IRAM in such a way that it can be later freed. It assumes both the address as well as the length to be word-aligned.
 40    It returns a region that's 1 word smaller than the region given because it stores the original Dram address there.
 41  */
 42  IRAM_ATTR static void *dram_alloc_to_iram_addr(void *addr, size_t len)
 43  {
 44      uintptr_t dstart = (uintptr_t)addr; //First word
 45      uintptr_t dend = dstart + len - 4; //Last word
 46      assert(esp_ptr_in_diram_dram((void *)dstart));
 47      assert(esp_ptr_in_diram_dram((void *)dend));
 48      assert((dstart & 3) == 0);
 49      assert((dend & 3) == 0);
 50  #if SOC_DIRAM_INVERTED // We want the word before the result to hold the DRAM address
 51      uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dend);
 52  #else
 53      uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dstart);
 54  #endif
 55      *iptr = dstart;
 56      return iptr + 1;
 57  }
 58  
 59  
 60  static void heap_caps_alloc_failed(size_t requested_size, uint32_t caps, const char *function_name) 
 61  {
 62      if (alloc_failed_callback) {
 63          alloc_failed_callback(requested_size, caps, function_name);
 64      }
 65  
 66      #ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
 67      esp_system_abort("Memory allocation failed");
 68      #endif
 69  }
 70  
 71  esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback)
 72  {
 73      if (callback == NULL) {
 74          return ESP_ERR_INVALID_ARG;
 75      }
 76  
 77      alloc_failed_callback = callback;
 78  
 79      return ESP_OK;
 80  }
 81  
 82  bool heap_caps_match(const heap_t *heap, uint32_t caps)
 83  {
 84      return heap->heap != NULL && ((get_all_caps(heap) & caps) == caps);
 85  }
 86  
 87  /*
 88  Routine to allocate a bit of memory with certain capabilities. caps is a bitfield of MALLOC_CAP_* bits.
 89  */
 90  IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps )
 91  {
 92      void *ret = NULL;
 93  
 94      if (size > HEAP_SIZE_MAX) {
 95          // Avoids int overflow when adding small numbers to size, or
 96          // calculating 'end' from start+size, by limiting 'size' to the possible range
 97          heap_caps_alloc_failed(size, caps, __func__);
 98  
 99          return NULL;
100      }
101  
102      if (caps & MALLOC_CAP_EXEC) {
103          //MALLOC_CAP_EXEC forces an alloc from IRAM. There is a region which has both this as well as the following
104          //caps, but the following caps are not possible for IRAM.  Thus, the combination is impossible and we return
105          //NULL directly, even although our heap capabilities (based on soc_memory_tags & soc_memory_regions) would
106          //indicate there is a tag for this.
107          if ((caps & MALLOC_CAP_8BIT) || (caps & MALLOC_CAP_DMA)) {
108              heap_caps_alloc_failed(size, caps, __func__);
109  
110              return NULL;
111          }
112          caps |= MALLOC_CAP_32BIT; // IRAM is 32-bit accessible RAM
113      }
114  
115      if (caps & MALLOC_CAP_32BIT) {
116          /* 32-bit accessible RAM should allocated in 4 byte aligned sizes
117           * (Future versions of ESP-IDF should possibly fail if an invalid size is requested)
118           */
119          size = (size + 3) & (~3); // int overflow checked above
120      }
121  
122      for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
123          //Iterate over heaps and check capabilities at this priority
124          heap_t *heap;
125          SLIST_FOREACH(heap, &registered_heaps, next) {
126              if (heap->heap == NULL) {
127                  continue;
128              }
129              if ((heap->caps[prio] & caps) != 0) {
130                  //Heap has at least one of the caps requested. If caps has other bits set that this prio
131                  //doesn't cover, see if they're available in other prios.
132                  if ((get_all_caps(heap) & caps) == caps) {
133                      //This heap can satisfy all the requested capabilities. See if we can grab some memory using it.
134                      if ((caps & MALLOC_CAP_EXEC) && esp_ptr_in_diram_dram((void *)heap->start)) {
135                          //This is special, insofar that what we're going to get back is a DRAM address. If so,
136                          //we need to 'invert' it (lowest address in DRAM == highest address in IRAM and vice-versa) and
137                          //add a pointer to the DRAM equivalent before the address we're going to return.
138                          ret = multi_heap_malloc(heap->heap, size + 4);  // int overflow checked above
139  
140                          if (ret != NULL) {
141                              return dram_alloc_to_iram_addr(ret, size + 4);  // int overflow checked above
142                          }
143                      } else {
144                          //Just try to alloc, nothing special.
145                          ret = multi_heap_malloc(heap->heap, size);
146                          if (ret != NULL) {
147                              return ret;
148                          }
149                      }
150                  }
151              }
152          }
153      }
154  
155      heap_caps_alloc_failed(size, caps, __func__);
156  
157      //Nothing usable found.
158      return NULL;
159  }
160  
161  
162  #define MALLOC_DISABLE_EXTERNAL_ALLOCS -1
163  //Dual-use: -1 (=MALLOC_DISABLE_EXTERNAL_ALLOCS) disables allocations in external memory, >=0 sets the limit for allocations preferring internal memory.
164  static int malloc_alwaysinternal_limit=MALLOC_DISABLE_EXTERNAL_ALLOCS;
165  
166  void heap_caps_malloc_extmem_enable(size_t limit)
167  {
168      malloc_alwaysinternal_limit=limit;
169  }
170  
171  /*
172   Default memory allocation implementation. Should return standard 8-bit memory. malloc() essentially resolves to this function.
173  */
174  IRAM_ATTR void *heap_caps_malloc_default( size_t size )
175  {
176      if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {
177          return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
178      } else {
179          void *r;
180          if (size <= malloc_alwaysinternal_limit) {
181              r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
182          } else {
183              r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM );
184          }
185          if (r==NULL) {
186              //try again while being less picky
187              r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT );
188          }
189          return r;
190      }
191  }
192  
193  /*
194   Same for realloc()
195   Note: keep the logic in here the same as in heap_caps_malloc_default (or merge the two as soon as this gets more complex...)
196   */
197  IRAM_ATTR void *heap_caps_realloc_default( void *ptr, size_t size )
198  {
199      if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {
200          return heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
201      } else {
202          void *r;
203          if (size <= malloc_alwaysinternal_limit) {
204              r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
205          } else {
206              r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM );
207          }
208          if (r==NULL && size>0) {
209              //We needed to allocate memory, but we didn't. Try again while being less picky.
210              r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT );
211          }
212          return r;
213      }
214  }
215  
216  /*
217   Memory allocation as preference in decreasing order.
218   */
219  IRAM_ATTR void *heap_caps_malloc_prefer( size_t size, size_t num, ... )
220  {
221      va_list argp;
222      va_start( argp, num );
223      void *r = NULL;
224      while (num--) {
225          uint32_t caps = va_arg( argp, uint32_t );
226          r = heap_caps_malloc( size, caps );
227          if (r != NULL) {
228              break;
229          }
230      }
231      va_end( argp );
232      return r;
233  }
234  
235  /*
236   Memory reallocation as preference in decreasing order.
237   */
238  IRAM_ATTR void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... )
239  {
240      va_list argp;
241      va_start( argp, num );
242      void *r = NULL;
243      while (num--) {
244          uint32_t caps = va_arg( argp, uint32_t );
245          r = heap_caps_realloc( ptr, size, caps );
246          if (r != NULL || size == 0) {
247              break;
248          }
249      }
250      va_end( argp );
251      return r;
252  }
253  
254  /*
255   Memory callocation as preference in decreasing order.
256   */
257  IRAM_ATTR void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... )
258  {
259      va_list argp;
260      va_start( argp, num );
261      void *r = NULL;
262      while (num--) {
263          uint32_t caps = va_arg( argp, uint32_t );
264          r = heap_caps_calloc( n, size, caps );
265          if (r != NULL) break;
266      }
267      va_end( argp );
268      return r;
269  }
270  
271  /* Find the heap which belongs to ptr, or return NULL if it's
272     not in any heap.
273  
274     (This confirms if ptr is inside the heap's region, doesn't confirm if 'ptr'
275     is an allocated block or is some other random address inside the heap.)
276  */
277  IRAM_ATTR static heap_t *find_containing_heap(void *ptr )
278  {
279      intptr_t p = (intptr_t)ptr;
280      heap_t *heap;
281      SLIST_FOREACH(heap, &registered_heaps, next) {
282          if (heap->heap != NULL && p >= heap->start && p < heap->end) {
283              return heap;
284          }
285      }
286      return NULL;
287  }
288  
289  IRAM_ATTR void heap_caps_free( void *ptr)
290  {
291      if (ptr == NULL) {
292          return;
293      }
294  
295      if (esp_ptr_in_diram_iram(ptr)) {
296          //Memory allocated here is actually allocated in the DRAM alias region and
297          //cannot be de-allocated as usual. dram_alloc_to_iram_addr stores a pointer to
298          //the equivalent DRAM address, though; free that.
299          uint32_t *dramAddrPtr = (uint32_t *)ptr;
300          ptr = (void *)dramAddrPtr[-1];
301      }
302  
303      heap_t *heap = find_containing_heap(ptr);
304      assert(heap != NULL && "free() target pointer is outside heap areas");
305      multi_heap_free(heap->heap, ptr);
306  }
307  
308  IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, int caps)
309  {
310      bool ptr_in_diram_case = false;
311      heap_t *heap = NULL;
312      void *dram_ptr = NULL;
313      
314      if (ptr == NULL) {
315          return heap_caps_malloc(size, caps);
316      }
317  
318      if (size == 0) {
319          heap_caps_free(ptr);
320          return NULL;
321      }
322  
323      if (size > HEAP_SIZE_MAX) {
324          heap_caps_alloc_failed(size, caps, __func__);
325  
326          return NULL;
327      }
328  
329      //The pointer to memory may be aliased, we need to 
330      //recover the corresponding address before to manage a new allocation:
331      if(esp_ptr_in_diram_iram((void *)ptr)) {
332          uint32_t *dram_addr = (uint32_t *)ptr;
333          dram_ptr  = (void *)dram_addr[-1];
334          
335          heap = find_containing_heap(dram_ptr);
336          assert(heap != NULL && "realloc() pointer is outside heap areas");
337          
338          //with pointers that reside on diram space, we avoid using 
339          //the realloc implementation due to address translation issues,
340          //instead force a malloc/copy/free
341          ptr_in_diram_case = true;
342      
343      } else {
344          heap = find_containing_heap(ptr);
345          assert(heap != NULL && "realloc() pointer is outside heap areas");
346      }
347  
348      // are the existing heap's capabilities compatible with the
349      // requested ones?
350      bool compatible_caps = (caps & get_all_caps(heap)) == caps;
351  
352      if (compatible_caps && !ptr_in_diram_case) {
353          // try to reallocate this memory within the same heap
354          // (which will resize the block if it can)
355          void *r = multi_heap_realloc(heap->heap, ptr, size);
356          if (r != NULL) {
357              return r;
358          }
359      }
360  
361      // if we couldn't do that, try to see if we can reallocate
362      // in a different heap with requested capabilities.
363      void *new_p = heap_caps_malloc(size, caps);
364      if (new_p != NULL) {
365          size_t old_size = 0;
366  
367          //If we're dealing with aliased ptr, information regarding its containing
368          //heap can only be obtained with translated address.
369          if(ptr_in_diram_case) {
370              old_size = multi_heap_get_allocated_size(heap->heap, dram_ptr);
371          } else {
372              old_size = multi_heap_get_allocated_size(heap->heap, ptr);
373          }
374  
375          assert(old_size > 0);
376          memcpy(new_p, ptr, MIN(size, old_size));
377          heap_caps_free(ptr);
378          return new_p;
379      }
380  
381      heap_caps_alloc_failed(size, caps, __func__);
382  
383      return NULL;
384  }
385  
386  IRAM_ATTR void *heap_caps_calloc( size_t n, size_t size, uint32_t caps)
387  {
388      void *result;
389      size_t size_bytes;
390  
391      if (__builtin_mul_overflow(n, size, &size_bytes)) {
392          return NULL;
393      }
394  
395      result = heap_caps_malloc(size_bytes, caps);
396      if (result != NULL) {
397          bzero(result, size_bytes);
398      }
399      return result;
400  }
401  
402  size_t heap_caps_get_total_size(uint32_t caps)
403  {
404      size_t total_size = 0;
405      heap_t *heap;
406      SLIST_FOREACH(heap, &registered_heaps, next) {
407          if (heap_caps_match(heap, caps)) {
408              total_size += (heap->end - heap->start);
409          }
410      }
411      return total_size;
412  }
413  
414  size_t heap_caps_get_free_size( uint32_t caps )
415  {
416      size_t ret = 0;
417      heap_t *heap;
418      SLIST_FOREACH(heap, &registered_heaps, next) {
419          if (heap_caps_match(heap, caps)) {
420              ret += multi_heap_free_size(heap->heap);
421          }
422      }
423      return ret;
424  }
425  
426  size_t heap_caps_get_minimum_free_size( uint32_t caps )
427  {
428      size_t ret = 0;
429      heap_t *heap;
430      SLIST_FOREACH(heap, &registered_heaps, next) {
431          if (heap_caps_match(heap, caps)) {
432              ret += multi_heap_minimum_free_size(heap->heap);
433          }
434      }
435      return ret;
436  }
437  
438  size_t heap_caps_get_largest_free_block( uint32_t caps )
439  {
440      multi_heap_info_t info;
441      heap_caps_get_info(&info, caps);
442      return info.largest_free_block;
443  }
444  
445  void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps )
446  {
447      bzero(info, sizeof(multi_heap_info_t));
448  
449      heap_t *heap;
450      SLIST_FOREACH(heap, &registered_heaps, next) {
451          if (heap_caps_match(heap, caps)) {
452              multi_heap_info_t hinfo;
453              multi_heap_get_info(heap->heap, &hinfo);
454  
455              info->total_free_bytes += hinfo.total_free_bytes;
456              info->total_allocated_bytes += hinfo.total_allocated_bytes;
457              info->largest_free_block = MAX(info->largest_free_block,
458                                             hinfo.largest_free_block);
459              info->minimum_free_bytes += hinfo.minimum_free_bytes;
460              info->allocated_blocks += hinfo.allocated_blocks;
461              info->free_blocks += hinfo.free_blocks;
462              info->total_blocks += hinfo.total_blocks;
463          }
464      }
465  }
466  
467  void heap_caps_print_heap_info( uint32_t caps )
468  {
469      multi_heap_info_t info;
470      printf("Heap summary for capabilities 0x%08X:\n", caps);
471      heap_t *heap;
472      SLIST_FOREACH(heap, &registered_heaps, next) {
473          if (heap_caps_match(heap, caps)) {
474              multi_heap_get_info(heap->heap, &info);
475  
476              printf("  At 0x%08x len %d free %d allocated %d min_free %d\n",
477                     heap->start, heap->end - heap->start, info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes);
478              printf("    largest_free_block %d alloc_blocks %d free_blocks %d total_blocks %d\n",
479                     info.largest_free_block, info.allocated_blocks,
480                     info.free_blocks, info.total_blocks);
481          }
482      }
483      printf("  Totals:\n");
484      heap_caps_get_info(&info, caps);
485  
486      printf("    free %d allocated %d min_free %d largest_free_block %d\n", info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes, info.largest_free_block);
487  }
488  
489  bool heap_caps_check_integrity(uint32_t caps, bool print_errors)
490  {
491      bool all_heaps = caps & MALLOC_CAP_INVALID;
492      bool valid = true;
493  
494      heap_t *heap;
495      SLIST_FOREACH(heap, &registered_heaps, next) {
496          if (heap->heap != NULL
497              && (all_heaps || (get_all_caps(heap) & caps) == caps)) {
498              valid = multi_heap_check(heap->heap, print_errors) && valid;
499          }
500      }
501  
502      return valid;
503  }
504  
505  bool heap_caps_check_integrity_all(bool print_errors)
506  {
507      return heap_caps_check_integrity(MALLOC_CAP_INVALID, print_errors);
508  }
509  
510  bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors)
511  {
512      heap_t *heap = find_containing_heap((void *)addr);
513      if (heap == NULL) {
514          return false;
515      }
516      return multi_heap_check(heap->heap, print_errors);
517  }
518  
519  void heap_caps_dump(uint32_t caps)
520  {
521      bool all_heaps = caps & MALLOC_CAP_INVALID;
522      heap_t *heap;
523      SLIST_FOREACH(heap, &registered_heaps, next) {
524          if (heap->heap != NULL
525              && (all_heaps || (get_all_caps(heap) & caps) == caps)) {
526              multi_heap_dump(heap->heap);
527          }
528      }
529  }
530  
531  void heap_caps_dump_all(void)
532  {
533      heap_caps_dump(MALLOC_CAP_INVALID);
534  }
535  
536  size_t heap_caps_get_allocated_size( void *ptr )
537  {
538      heap_t *heap = find_containing_heap(ptr);
539      size_t size = multi_heap_get_allocated_size(heap->heap, ptr);
540      return size;
541  }
542  
543  IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, int caps)
544  {
545      void *ret = NULL;
546  
547      if(!alignment) {
548          return NULL;
549      }
550  
551      //Alignment must be a power of two:
552      if((alignment & (alignment - 1)) != 0) {
553          return NULL;
554      }
555  
556      if (size > HEAP_SIZE_MAX) {
557          // Avoids int overflow when adding small numbers to size, or
558          // calculating 'end' from start+size, by limiting 'size' to the possible range
559          heap_caps_alloc_failed(size, caps, __func__);
560  
561          return NULL;
562      }
563  
564      //aligned alloc for now only supports default allocator or external
565      //allocator.
566      if((caps & (MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM)) == 0) {
567          heap_caps_alloc_failed(size, caps, __func__);
568          return NULL;
569      }
570  
571      //if caps requested are supported, clear undesired others:
572      caps &= (MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM);
573  
574      for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
575          //Iterate over heaps and check capabilities at this priority
576          heap_t *heap;
577          SLIST_FOREACH(heap, &registered_heaps, next) {
578              if (heap->heap == NULL) {
579                  continue;
580              }
581              if ((heap->caps[prio] & caps) != 0) {
582                  //Heap has at least one of the caps requested. If caps has other bits set that this prio
583                  //doesn't cover, see if they're available in other prios.
584                  if ((get_all_caps(heap) & caps) == caps) {
585                      //Just try to alloc, nothing special.
586                      ret = multi_heap_aligned_alloc(heap->heap, size, alignment); 
587                      if (ret != NULL) {
588                          return ret;
589                      }
590                  }
591              }
592          }
593      }
594  
595      heap_caps_alloc_failed(size, caps, __func__);
596  
597      //Nothing usable found.
598      return NULL;
599  }
600  
601  IRAM_ATTR void heap_caps_aligned_free(void *ptr)
602  {
603      heap_caps_free(ptr);
604  }
605  
606  void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps)
607  {    
608      size_t size_bytes;
609      if (__builtin_mul_overflow(n, size, &size_bytes)) {
610          return NULL;
611      }
612  
613      void *ptr = heap_caps_aligned_alloc(alignment,size_bytes, caps);
614      if(ptr != NULL) {
615          memset(ptr, 0, size_bytes);
616      }
617  
618      return ptr;
619  }