/*1* Copyright (c) 2019-2020 CTCaer2*3* This program is free software; you can redistribute it and/or modify it4* under the terms and conditions of the GNU General Public License,5* version 2, as published by the Free Software Foundation.6*7* This program is distributed in the hope it will be useful, but WITHOUT8* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or9* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for10* more details.11*12* You should have received a copy of the GNU General Public License13* along with this program. If not, see <http://www.gnu.org/licenses/>.14*/1516/**17* @file lv_mem.c18* General and portable implementation of malloc and free.19* The dynamic memory monitoring is also supported.20*/2122/*********************23* INCLUDES24*********************/25#include "lv_mem.h"26#include "lv_math.h"27#include <string.h>2829#include <assert.h>3031#if LV_MEM_CUSTOM != 032#include LV_MEM_CUSTOM_INCLUDE33#endif3435/*********************36* DEFINES37*********************/38#define LV_MEM_ADD_JUNK 0 /*Add memory junk on alloc (0xaa) and free(0xbb) (just for testing purposes)*/394041#ifdef LV_MEM_ENV6442# define MEM_UNIT uint64_t43#else44# define MEM_UNIT uint32_t45#endif464748/**********************49* TYPEDEFS50**********************/5152#if LV_ENABLE_GC == 0 /*gc custom allocations must not include header*/5354/*The size of this union must be 32 bytes (uint32_t * 8)*/55typedef union {56struct {57MEM_UNIT used: 1; //1: if the entry is used58MEM_UNIT d_size: 31; //Size of the data59};60MEM_UNIT header; //The header (used + d_size)61MEM_UNIT align[8]; //Align header size to MEM_UNIT * 8 bytes62} lv_mem_header_t;6364static_assert(sizeof(lv_mem_header_t) == 32, "Node header must be 32 bytes!");6566typedef struct {67lv_mem_header_t header;68uint8_t first_data; /*First data byte in the allocated data (Just for easily create a pointer)*/69} lv_mem_ent_t;7071#endif /* LV_ENABLE_GC */7273/**********************74* STATIC PROTOTYPES75**********************/76#if LV_MEM_CUSTOM == 077static lv_mem_ent_t * ent_get_next(lv_mem_ent_t * act_e);78static void * ent_alloc(lv_mem_ent_t * e, uint32_t size);79static void ent_trunc(lv_mem_ent_t * e, uint32_t size);80#endif8182/**********************83* STATIC VARIABLES84**********************/85#if LV_MEM_CUSTOM == 086static uint8_t * work_mem;87#endif8889static uint32_t zero_mem; /*Give the address of this variable if 0 byte should be allocated*/9091/**********************92* MACROS93**********************/9495/**********************96* GLOBAL FUNCTIONS97**********************/9899/**100* Initiaiize the dyn_mem module (work memory and other variables)101*/102void lv_mem_init(void)103{104#if LV_MEM_CUSTOM == 0105106#if LV_MEM_ADR == 0107/*Allocate a large array to store the dynamically allocated data*/108static LV_MEM_ATTR MEM_UNIT work_mem_int[LV_MEM_SIZE / sizeof(MEM_UNIT)];109work_mem = (uint8_t *) work_mem_int;110#else111work_mem = (uint8_t *) LV_MEM_ADR;112#endif113114lv_mem_ent_t * full = (lv_mem_ent_t *)work_mem;115full->header.used = 0;116/*The total mem size id reduced by the first header and the close patterns */117full->header.d_size = LV_MEM_SIZE - sizeof(lv_mem_header_t);118#endif119}120121/**122* Allocate a memory dynamically123* @param size size of the memory to allocate in bytes124* @return pointer to the allocated memory125*/126void * lv_mem_alloc(uint32_t size)127{128if(size == 0) {129return &zero_mem;130}131132/*Round the size to lv_mem_header_t*/133if(size & (sizeof(lv_mem_header_t) - 1)) {134size = size & (~(sizeof(lv_mem_header_t) - 1));135size += sizeof(lv_mem_header_t);136}137138void * alloc = NULL;139140#if LV_MEM_CUSTOM == 0 /*Use the allocation from dyn_mem*/141lv_mem_ent_t * e = NULL;142143//Search for a appropriate entry144do {145//Get the next entry146e = ent_get_next(e);147148/*If there is next entry then try to allocate there*/149if(e != NULL) {150alloc = ent_alloc(e, size);151}152//End if there is not next entry OR the alloc. is successful153} while(e != NULL && alloc == NULL);154155156#else /*Use custom, user defined malloc function*/157#if LV_ENABLE_GC == 1 /*gc must not include header*/158alloc = LV_MEM_CUSTOM_ALLOC(size);159#else /* LV_ENABLE_GC */160/*Allocate a header too to store the size*/161alloc = LV_MEM_CUSTOM_ALLOC(size + sizeof(lv_mem_header_t));162if(alloc != NULL) {163((lv_mem_ent_t *) alloc)->header.d_size = size;164((lv_mem_ent_t *) alloc)->header.used = 1;165alloc = &((lv_mem_ent_t *) alloc)->first_data;166}167#endif /* LV_ENABLE_GC */168#endif /* LV_MEM_CUSTOM */169170#if LV_MEM_ADD_JUNK171if(alloc != NULL) memset(alloc, 0xaa, size);172#endif173174if(alloc == NULL) LV_LOG_WARN("Couldn't allocate memory");175176return alloc;177}178179/**180* Free an allocated data181* @param data pointer to an allocated memory182*/183void lv_mem_free(const void * data)184{185if(data == &zero_mem) return;186if(data == NULL) return;187188189#if LV_MEM_ADD_JUNK190memset((void *)data, 0xbb, lv_mem_get_size(data));191#endif192193#if LV_ENABLE_GC==0194/*e points to the header*/195lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *) data - sizeof(lv_mem_header_t));196e->header.used = 0;197#endif198199#if LV_MEM_CUSTOM == 0200#if LV_MEM_AUTO_DEFRAG201/* Make a simple defrag.202* Join the following free entries after this*/203lv_mem_ent_t * e_next;204e_next = ent_get_next(e);205while(e_next != NULL) {206if(e_next->header.used == 0) {207e->header.d_size += e_next->header.d_size + sizeof(e->header);208} else {209break;210}211e_next = ent_get_next(e_next);212}213#endif214#else /*Use custom, user defined free function*/215#if LV_ENABLE_GC==0216LV_MEM_CUSTOM_FREE(e);217#else218LV_MEM_CUSTOM_FREE((void*)data);219#endif /*LV_ENABLE_GC*/220#endif221}222223/**224* Reallocate a memory with a new size. The old content will be kept.225* @param data pointer to an allocated memory.226* Its content will be copied to the new memory block and freed227* @param new_size the desired new size in byte228* @return pointer to the new memory229*/230231#if LV_ENABLE_GC==0232233void * lv_mem_realloc(void * data_p, uint32_t new_size)234{235/*Round the size to lv_mem_header_t*/236if(new_size & (sizeof(lv_mem_header_t) - 1)) {237new_size = new_size & (~(sizeof(lv_mem_header_t) - 1));238new_size += sizeof(lv_mem_header_t);239}240241/*data_p could be previously freed pointer (in this case it is invalid)*/242if(data_p != NULL) {243lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *) data_p - sizeof(lv_mem_header_t));244if(e->header.used == 0) {245data_p = NULL;246}247}248249uint32_t old_size = lv_mem_get_size(data_p);250if(old_size == new_size) return data_p; /*Also avoid reallocating the same memory*/251252#if LV_MEM_CUSTOM == 0253/* Only truncate the memory is possible254* If the 'old_size' was extended by a header size in 'ent_trunc' it avoids reallocating this same memory */255if(new_size < old_size) {256lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *) data_p - sizeof(lv_mem_header_t));257ent_trunc(e, new_size);258return &e->first_data;259}260#endif261262void * new_p;263new_p = lv_mem_alloc(new_size);264265if(new_p != NULL && data_p != NULL) {266/*Copy the old data to the new. Use the smaller size*/267if(old_size != 0) {268memcpy(new_p, data_p, LV_MATH_MIN(new_size, old_size));269lv_mem_free(data_p);270}271}272273274if(new_p == NULL) LV_LOG_WARN("Couldn't allocate memory");275276return new_p;277}278279#else /* LV_ENABLE_GC */280281void * lv_mem_realloc(void * data_p, uint32_t new_size)282{283void * new_p = LV_MEM_CUSTOM_REALLOC(data_p, new_size);284if(new_p == NULL) LV_LOG_WARN("Couldn't allocate memory");285return new_p;286}287288#endif /* lv_enable_gc */289290/**291* Join the adjacent free memory blocks292*/293void lv_mem_defrag(void)294{295#if LV_MEM_CUSTOM == 0296lv_mem_ent_t * e_free;297lv_mem_ent_t * e_next;298e_free = ent_get_next(NULL);299300while(1) {301/*Search the next free entry*/302while(e_free != NULL) {303if(e_free->header.used != 0) {304e_free = ent_get_next(e_free);305} else {306break;307}308}309310if(e_free == NULL) return;311312/*Joint the following free entries to the free*/313e_next = ent_get_next(e_free);314while(e_next != NULL) {315if(e_next->header.used == 0) {316e_free->header.d_size += e_next->header.d_size + sizeof(e_next->header);317} else {318break;319}320321e_next = ent_get_next(e_next);322}323324if(e_next == NULL) return;325326/*Continue from the lastly checked entry*/327e_free = e_next;328}329#endif330}331332/**333* Give information about the work memory of dynamic allocation334* @param mon_p pointer to a dm_mon_p variable,335* the result of the analysis will be stored here336*/337void lv_mem_monitor(lv_mem_monitor_t * mon_p)338{339/*Init the data*/340memset(mon_p, 0, sizeof(lv_mem_monitor_t));341#if LV_MEM_CUSTOM == 0342lv_mem_ent_t * e;343e = NULL;344345e = ent_get_next(e);346347while(e != NULL) {348if(e->header.used == 0) {349mon_p->free_cnt++;350mon_p->free_size += e->header.d_size;351if(e->header.d_size > mon_p->free_biggest_size) {352mon_p->free_biggest_size = e->header.d_size;353}354} else {355mon_p->used_cnt++;356}357358e = ent_get_next(e);359}360mon_p->total_size = LV_MEM_SIZE;361mon_p->used_pct = 100 - ((uint64_t)100U * mon_p->free_size) / mon_p->total_size;362mon_p->frag_pct = (uint32_t)mon_p->free_biggest_size * 100U / mon_p->free_size;363mon_p->frag_pct = 100 - mon_p->frag_pct;364#endif365}366367/**368* Give the size of an allocated memory369* @param data pointer to an allocated memory370* @return the size of data memory in bytes371*/372373#if LV_ENABLE_GC==0374375uint32_t lv_mem_get_size(const void * data)376{377if(data == NULL) return 0;378if(data == &zero_mem) return 0;379380lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *) data - sizeof(lv_mem_header_t));381382return e->header.d_size;383}384385#else /* LV_ENABLE_GC */386387uint32_t lv_mem_get_size(const void * data)388{389return LV_MEM_CUSTOM_GET_SIZE(data);390}391392#endif /*LV_ENABLE_GC*/393394/**********************395* STATIC FUNCTIONS396**********************/397398#if LV_MEM_CUSTOM == 0399/**400* Give the next entry after 'act_e'401* @param act_e pointer to an entry402* @return pointer to an entry after 'act_e'403*/404static lv_mem_ent_t * ent_get_next(lv_mem_ent_t * act_e)405{406lv_mem_ent_t * next_e = NULL;407408if(act_e == NULL) { /*NULL means: get the first entry*/409next_e = (lv_mem_ent_t *) work_mem;410} else { /*Get the next entry */411uint8_t * data = &act_e->first_data;412next_e = (lv_mem_ent_t *)&data[act_e->header.d_size];413414if(&next_e->first_data >= &work_mem[LV_MEM_SIZE]) next_e = NULL;415}416417return next_e;418}419420421/**422* Try to do the real allocation with a given size423* @param e try to allocate to this entry424* @param size size of the new memory in bytes425* @return pointer to the allocated memory or NULL if not enough memory in the entry426*/427static void * ent_alloc(lv_mem_ent_t * e, uint32_t size)428{429void * alloc = NULL;430431/*If the memory is free and big enough then use it */432if(e->header.used == 0 && e->header.d_size >= size) {433/*Truncate the entry to the desired size */434ent_trunc(e, size),435436e->header.used = 1;437438/*Save the allocated data*/439alloc = &e->first_data;440}441442return alloc;443}444445/**446* Truncate the data of entry to the given size447* @param e Pointer to an entry448* @param size new size in bytes449*/450static void ent_trunc(lv_mem_ent_t * e, uint32_t size)451{452/*Don't let empty space only for a header without data*/453if(e->header.d_size == size + sizeof(lv_mem_header_t)) {454size = e->header.d_size;455}456457/* Create the new entry after the current if there is space for it */458if(e->header.d_size != size) {459uint8_t * e_data = &e->first_data;460lv_mem_ent_t * after_new_e = (lv_mem_ent_t *)&e_data[size];461after_new_e->header.used = 0;462after_new_e->header.d_size = e->header.d_size - size - sizeof(lv_mem_header_t);463}464465/* Set the new size for the original entry */466e->header.d_size = size;467}468469#endif470471472