/* * Copyright (C) 2016 The Android Open Source Project * * 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 <trylock.h> #include <atomic.h> #include <stdint.h> #include <stdio.h> #include <heap.h> #include <seos.h> #define TIDX_HEAP_EXTRA 2 // must be >= 0; best if > 0, don't make it > 7, since it unnecessarily limits max heap size we can manage #define TIDX_HEAP_BITS (TASK_IDX_BITS + TIDX_HEAP_EXTRA) #define TIDX_MASK ((1 << TIDX_HEAP_BITS) - 1) #define MAX_HEAP_ORDER (31 - TIDX_HEAP_BITS) #if MAX_HEAP_ORDER < 16 # error Too little HEAP is available #endif struct HeapNode { struct HeapNode* prev; uint32_t size: MAX_HEAP_ORDER; uint32_t used: 1; uint32_t tidx: TIDX_HEAP_BITS; // TASK_IDX_BITS to uniquely identify task; + extra bits of redundant counter add extra protection uint8_t data[]; }; #ifdef FORCE_HEAP_IN_DOT_DATA static uint8_t __attribute__ ((aligned (8))) gHeap[HEAP_SIZE]; #define REAL_HEAP_SIZE ((HEAP_SIZE) &~ 7) #define ALIGNED_HEAP_START (&gHeap) #else extern uint8_t __heap_end[], __heap_start[]; #define ALIGNED_HEAP_START (uint8_t*)((((uintptr_t)&__heap_start) + 7) &~ 7) #define ALIGNED_HEAP_END (uint8_t*)(((uintptr_t)&__heap_end) &~ 7) #define REAL_HEAP_SIZE (ALIGNED_HEAP_END - ALIGNED_HEAP_START) #endif static struct HeapNode* gHeapHead; static TRYLOCK_DECL_STATIC(gHeapLock) = TRYLOCK_INIT_STATIC(); static volatile uint8_t gNeedFreeMerge = false; /* cannot be bool since its size is ill defined */ static struct HeapNode *gHeapTail; static inline struct HeapNode* heapPrvGetNext(struct HeapNode* node) { return (gHeapTail == node) ? NULL : (struct HeapNode*)(node->data + node->size); } bool heapInit(void) { uint32_t size = REAL_HEAP_SIZE; struct HeapNode* node; node = gHeapHead = (struct HeapNode*)ALIGNED_HEAP_START; if (size < sizeof(struct HeapNode)) return false; gHeapTail = node; node->used = 0; node->prev = NULL; node->size = size - sizeof(struct HeapNode); return true; } //called to merge free chunks in case free() was unable to last time it tried. only call with lock held please static void heapMergeFreeChunks(void) { while (atomicXchgByte(&gNeedFreeMerge, false)) { struct HeapNode *node = gHeapHead, *next; while (node) { next = heapPrvGetNext(node); if (!node->used && next && !next->used) { /* merged */ node->size += sizeof(struct HeapNode) + next->size; next = heapPrvGetNext(node); if (next) next->prev = node; else gHeapTail = node; } else node = next; } } } void* heapAlloc(uint32_t sz) { struct HeapNode *node, *best = NULL; void* ret = NULL; if (!trylockTryTake(&gHeapLock)) return NULL; /* merge free chunks to help better use space */ heapMergeFreeChunks(); sz = (sz + 3) &~ 3; node = gHeapHead; while (node) { if (!node->used && node->size >= sz && (!best || best->size > node->size)) { best = node; if (best->size == sz) break; } node = heapPrvGetNext(node); } if (!best) //alloc failed goto out; if (best->size - sz > sizeof(struct HeapNode)) { //there is a point to split up the chunk node = (struct HeapNode*)(best->data + sz); node->used = 0; node->tidx = 0; node->size = best->size - sz - sizeof(struct HeapNode); node->prev = best; if (best != gHeapTail) heapPrvGetNext(node)->prev = node; else gHeapTail = node; best->size = sz; } best->used = 1; best->tidx = osGetCurrentTid(); ret = best->data; out: trylockRelease(&gHeapLock); return ret; } void heapFree(void* ptr) { struct HeapNode *node, *t; bool haveLock; if (ptr == NULL) { // NULL is a valid reply from heapAlloc, and thus it is not an error for // us to receive it here. We just ignore it. return; } haveLock = trylockTryTake(&gHeapLock); node = ((struct HeapNode*)ptr) - 1; node->used = 0; node->tidx = 0; if (haveLock) { while (node->prev && !node->prev->used) node = node->prev; while ((t = heapPrvGetNext(node)) && !t->used) { node->size += sizeof(struct HeapNode) + t->size; if (gHeapTail == t) gHeapTail = node; } if ((t = heapPrvGetNext(node))) t->prev = node; trylockRelease(&gHeapLock); } else gNeedFreeMerge = true; } int heapFreeAll(uint32_t tid) { struct HeapNode *node; bool haveLock; int count = 0; if (!tid) return -1; // this can only fail if called from interrupt haveLock = trylockTryTake(&gHeapLock); if (!haveLock) return -1; tid &= TIDX_MASK; for (node = gHeapHead; node; node = heapPrvGetNext(node)) { if (node->tidx == tid) { node->used = 0; node->tidx = 0; count++; } } gNeedFreeMerge = count > 0; trylockRelease(&gHeapLock); return count; } int heapGetFreeSize(int *numChunks, int *largestChunk) { struct HeapNode *node; bool haveLock; int bytes = 0; *numChunks = *largestChunk = 0; // this can only fail if called from interrupt haveLock = trylockTryTake(&gHeapLock); if (!haveLock) return -1; for (node = gHeapHead; node; node = heapPrvGetNext(node)) { if (!node->used) { if (node->size > *largestChunk) *largestChunk = node->size; bytes += node->size + sizeof(struct HeapNode); (*numChunks)++; } } trylockRelease(&gHeapLock); return bytes; } int heapGetTaskSize(uint32_t tid) { struct HeapNode *node; bool haveLock; int bytes = 0; // this can only fail if called from interrupt haveLock = trylockTryTake(&gHeapLock); if (!haveLock) return -1; tid &= TIDX_MASK; for (node = gHeapHead; node; node = heapPrvGetNext(node)) { if (node->used && node->tidx == tid) { bytes += node->size + sizeof(struct HeapNode); } } trylockRelease(&gHeapLock); return bytes; }