1 /**
2  * @file lv_mem.c
3  * General and portable implementation of malloc and free.
4  * The dynamic memory monitoring is also supported.
5  */
6 
7 /*********************
8  *      INCLUDES
9  *********************/
10 #include "lv_mem.h"
11 #include "lv_math.h"
12 #include <string.h>
13 
14 #if LV_MEM_CUSTOM != 0
15 #include LV_MEM_CUSTOM_INCLUDE
16 #endif
17 
18 /*********************
19  *      DEFINES
20  *********************/
21 /*Add memory junk on alloc (0xaa) and free(0xbb) (just for testing purposes)*/
22 #define LV_MEM_ADD_JUNK 1
23 
24 #ifdef LV_MEM_ENV64
25 #define MEM_UNIT uint64_t
26 #else
27 #define MEM_UNIT uint32_t
28 #endif
29 
30 /**********************
31  *      TYPEDEFS
32  **********************/
33 
34 #if LV_ENABLE_GC == 0 /*gc custom allocations must not include header*/
35 
36 /*The size of this union must be 4 bytes (uint32_t)*/
37 typedef union
38 {
39     struct
40     {
41         MEM_UNIT used : 1;    /* 1: if the entry is used*/
42         MEM_UNIT d_size : 31; /* Size off the data (1 means 4 bytes)*/
43     } s;
44     MEM_UNIT header; /* The header (used + d_size)*/
45 } lv_mem_header_t;
46 
47 typedef struct
48 {
49     lv_mem_header_t header;
50     uint8_t first_data; /*First data byte in the allocated data (Just for easily create a pointer)*/
51 } lv_mem_ent_t;
52 
53 #endif /* LV_ENABLE_GC */
54 
55 /**********************
56  *  STATIC PROTOTYPES
57  **********************/
58 #if LV_MEM_CUSTOM == 0
59 static lv_mem_ent_t * ent_get_next(lv_mem_ent_t * act_e);
60 static void * ent_alloc(lv_mem_ent_t * e, uint32_t size);
61 static void ent_trunc(lv_mem_ent_t * e, uint32_t size);
62 #endif
63 
64 /**********************
65  *  STATIC VARIABLES
66  **********************/
67 #if LV_MEM_CUSTOM == 0
68 static uint8_t * work_mem;
69 #endif
70 
71 static uint32_t zero_mem; /*Give the address of this variable if 0 byte should be allocated*/
72 
73 /**********************
74  *      MACROS
75  **********************/
76 
77 /**********************
78  *   GLOBAL FUNCTIONS
79  **********************/
80 
81 /**
82  * Initiaiize the dyn_mem module (work memory and other variables)
83  */
lv_mem_init(void)84 void lv_mem_init(void)
85 {
86 #if LV_MEM_CUSTOM == 0
87 
88 #if LV_MEM_ADR == 0
89     /*Allocate a large array to store the dynamically allocated data*/
90     static LV_MEM_ATTR MEM_UNIT work_mem_int[LV_MEM_SIZE / sizeof(MEM_UNIT)];
91     work_mem = (uint8_t *)work_mem_int;
92 #else
93     work_mem = (uint8_t *)LV_MEM_ADR;
94 #endif
95 
96     lv_mem_ent_t * full = (lv_mem_ent_t *)work_mem;
97     full->header.s.used = 0;
98     /*The total mem size id reduced by the first header and the close patterns */
99     full->header.s.d_size = LV_MEM_SIZE - sizeof(lv_mem_header_t);
100 #endif
101 }
102 
103 /**
104  * Allocate a memory dynamically
105  * @param size size of the memory to allocate in bytes
106  * @return pointer to the allocated memory
107  */
lv_mem_alloc(uint32_t size)108 void * lv_mem_alloc(uint32_t size)
109 {
110     if(size == 0) {
111         return &zero_mem;
112     }
113 
114 #ifdef LV_MEM_ENV64
115     /*Round the size up to 8*/
116     if(size & 0x7) {
117         size = size & (~0x7);
118         size += 8;
119     }
120 #else
121     /*Round the size up to 4*/
122     if(size & 0x3) {
123         size = size & (~0x3);
124         size += 4;
125     }
126 #endif
127     void * alloc = NULL;
128 
129 #if LV_MEM_CUSTOM == 0
130     /*Use the built-in allocators*/
131     lv_mem_ent_t * e = NULL;
132 
133     /* Search for a appropriate entry*/
134     do {
135         /* Get the next entry*/
136         e = ent_get_next(e);
137 
138         /*If there is next entry then try to allocate there*/
139         if(e != NULL) {
140             alloc = ent_alloc(e, size);
141         }
142         /* End if there is not next entry OR the alloc. is successful*/
143     } while(e != NULL && alloc == NULL);
144 
145 #else
146 /*Use custom, user defined malloc function*/
147 #if LV_ENABLE_GC == 1 /*gc must not include header*/
148     alloc = LV_MEM_CUSTOM_ALLOC(size);
149 #else                 /* LV_ENABLE_GC */
150     /*Allocate a header too to store the size*/
151     alloc = LV_MEM_CUSTOM_ALLOC(size + sizeof(lv_mem_header_t));
152     if(alloc != NULL) {
153         ((lv_mem_ent_t *)alloc)->header.s.d_size = size;
154         ((lv_mem_ent_t *)alloc)->header.s.used   = 1;
155 
156         alloc = &((lv_mem_ent_t *)alloc)->first_data;
157     }
158 #endif                /* LV_ENABLE_GC */
159 #endif                /* LV_MEM_CUSTOM */
160 
161 #if LV_MEM_ADD_JUNK
162     if(alloc != NULL) memset(alloc, 0xaa, size);
163 #endif
164 
165     if(alloc == NULL) LV_LOG_WARN("Couldn't allocate memory");
166 
167     return alloc;
168 }
169 
170 /**
171  * Free an allocated data
172  * @param data pointer to an allocated memory
173  */
lv_mem_free(const void * data)174 void lv_mem_free(const void * data)
175 {
176     if(data == &zero_mem) return;
177     if(data == NULL) return;
178 
179 #if LV_MEM_ADD_JUNK
180     memset((void *)data, 0xbb, lv_mem_get_size(data));
181 #endif
182 
183 #if LV_ENABLE_GC == 0
184     /*e points to the header*/
185     lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *)data - sizeof(lv_mem_header_t));
186     e->header.s.used = 0;
187 #endif
188 
189 #if LV_MEM_CUSTOM == 0
190 #if LV_MEM_AUTO_DEFRAG
191     /* Make a simple defrag.
192      * Join the following free entries after this*/
193     lv_mem_ent_t * e_next;
194     e_next = ent_get_next(e);
195     while(e_next != NULL) {
196         if(e_next->header.s.used == 0) {
197             e->header.s.d_size += e_next->header.s.d_size + sizeof(e->header);
198         } else {
199             break;
200         }
201         e_next = ent_get_next(e_next);
202     }
203 #endif
204 #else /*Use custom, user defined free function*/
205 #if LV_ENABLE_GC == 0
206     LV_MEM_CUSTOM_FREE(e);
207 #else
208     LV_MEM_CUSTOM_FREE((void *)data);
209 #endif /*LV_ENABLE_GC*/
210 #endif
211 }
212 
213 /**
214  * Reallocate a memory with a new size. The old content will be kept.
215  * @param data pointer to an allocated memory.
216  * Its content will be copied to the new memory block and freed
217  * @param new_size the desired new size in byte
218  * @return pointer to the new memory
219  */
220 
221 #if LV_ENABLE_GC == 0
222 
lv_mem_realloc(void * data_p,uint32_t new_size)223 void * lv_mem_realloc(void * data_p, uint32_t new_size)
224 {
225     /*data_p could be previously freed pointer (in this case it is invalid)*/
226     if(data_p != NULL) {
227         lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *)data_p - sizeof(lv_mem_header_t));
228         if(e->header.s.used == 0) {
229             data_p = NULL;
230         }
231     }
232 
233     uint32_t old_size = lv_mem_get_size(data_p);
234     if(old_size == new_size) return data_p; /*Also avoid reallocating the same memory*/
235 
236 #if LV_MEM_CUSTOM == 0
237     /* Truncate the memory if the new size is smaller. */
238     if(new_size < old_size) {
239         lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *)data_p - sizeof(lv_mem_header_t));
240         ent_trunc(e, new_size);
241         return &e->first_data;
242     }
243 #endif
244 
245     void * new_p;
246     new_p = lv_mem_alloc(new_size);
247 
248     if(new_p != NULL && data_p != NULL) {
249         /*Copy the old data to the new. Use the smaller size*/
250         if(old_size != 0) {
251             memcpy(new_p, data_p, LV_MATH_MIN(new_size, old_size));
252             lv_mem_free(data_p);
253         }
254     }
255 
256     if(new_p == NULL) LV_LOG_WARN("Couldn't allocate memory");
257 
258     return new_p;
259 }
260 
261 #else /* LV_ENABLE_GC */
262 
lv_mem_realloc(void * data_p,uint32_t new_size)263 void * lv_mem_realloc(void * data_p, uint32_t new_size)
264 {
265     void * new_p = LV_MEM_CUSTOM_REALLOC(data_p, new_size);
266     if(new_p == NULL) LV_LOG_WARN("Couldn't allocate memory");
267     return new_p;
268 }
269 
270 #endif /* lv_enable_gc */
271 
272 /**
273  * Join the adjacent free memory blocks
274  */
lv_mem_defrag(void)275 void lv_mem_defrag(void)
276 {
277 #if LV_MEM_CUSTOM == 0
278     lv_mem_ent_t * e_free;
279     lv_mem_ent_t * e_next;
280     e_free = ent_get_next(NULL);
281 
282     while(1) {
283         /*Search the next free entry*/
284         while(e_free != NULL) {
285             if(e_free->header.s.used != 0) {
286                 e_free = ent_get_next(e_free);
287             } else {
288                 break;
289             }
290         }
291 
292         if(e_free == NULL) return;
293 
294         /*Joint the following free entries to the free*/
295         e_next = ent_get_next(e_free);
296         while(e_next != NULL) {
297             if(e_next->header.s.used == 0) {
298                 e_free->header.s.d_size += e_next->header.s.d_size + sizeof(e_next->header);
299             } else {
300                 break;
301             }
302 
303             e_next = ent_get_next(e_next);
304         }
305 
306         if(e_next == NULL) return;
307 
308         /*Continue from the lastly checked entry*/
309         e_free = e_next;
310     }
311 #endif
312 }
313 
314 /**
315  * Give information about the work memory of dynamic allocation
316  * @param mon_p pointer to a dm_mon_p variable,
317  *              the result of the analysis will be stored here
318  */
lv_mem_monitor(lv_mem_monitor_t * mon_p)319 void lv_mem_monitor(lv_mem_monitor_t * mon_p)
320 {
321     /*Init the data*/
322     memset(mon_p, 0, sizeof(lv_mem_monitor_t));
323 #if LV_MEM_CUSTOM == 0
324     lv_mem_ent_t * e;
325     e = NULL;
326 
327     e = ent_get_next(e);
328 
329     while(e != NULL) {
330         if(e->header.s.used == 0) {
331             mon_p->free_cnt++;
332             mon_p->free_size += e->header.s.d_size;
333             if(e->header.s.d_size > mon_p->free_biggest_size) {
334                 mon_p->free_biggest_size = e->header.s.d_size;
335             }
336         } else {
337             mon_p->used_cnt++;
338         }
339 
340         e = ent_get_next(e);
341     }
342     mon_p->total_size = LV_MEM_SIZE;
343     mon_p->used_pct   = 100 - (100U * mon_p->free_size) / mon_p->total_size;
344     mon_p->frag_pct   = (uint32_t)mon_p->free_biggest_size * 100U / mon_p->free_size;
345     mon_p->frag_pct   = 100 - mon_p->frag_pct;
346 #endif
347 }
348 
349 /**
350  * Give the size of an allocated memory
351  * @param data pointer to an allocated memory
352  * @return the size of data memory in bytes
353  */
354 
355 #if LV_ENABLE_GC == 0
356 
lv_mem_get_size(const void * data)357 uint32_t lv_mem_get_size(const void * data)
358 {
359     if(data == NULL) return 0;
360     if(data == &zero_mem) return 0;
361 
362     lv_mem_ent_t * e = (lv_mem_ent_t *)((uint8_t *)data - sizeof(lv_mem_header_t));
363 
364     return e->header.s.d_size;
365 }
366 
367 #else /* LV_ENABLE_GC */
368 
lv_mem_get_size(const void * data)369 uint32_t lv_mem_get_size(const void * data)
370 {
371     return LV_MEM_CUSTOM_GET_SIZE(data);
372 }
373 
374 #endif /*LV_ENABLE_GC*/
375 
376 /**********************
377  *   STATIC FUNCTIONS
378  **********************/
379 
380 #if LV_MEM_CUSTOM == 0
381 /**
382  * Give the next entry after 'act_e'
383  * @param act_e pointer to an entry
384  * @return pointer to an entry after 'act_e'
385  */
ent_get_next(lv_mem_ent_t * act_e)386 static lv_mem_ent_t * ent_get_next(lv_mem_ent_t * act_e)
387 {
388     lv_mem_ent_t * next_e = NULL;
389 
390     if(act_e == NULL) { /*NULL means: get the first entry*/
391         next_e = (lv_mem_ent_t *)work_mem;
392     } else { /*Get the next entry */
393         uint8_t * data = &act_e->first_data;
394         next_e         = (lv_mem_ent_t *)&data[act_e->header.s.d_size];
395 
396         if(&next_e->first_data >= &work_mem[LV_MEM_SIZE]) next_e = NULL;
397     }
398 
399     return next_e;
400 }
401 
402 /**
403  * Try to do the real allocation with a given size
404  * @param e try to allocate to this entry
405  * @param size size of the new memory in bytes
406  * @return pointer to the allocated memory or NULL if not enough memory in the entry
407  */
ent_alloc(lv_mem_ent_t * e,uint32_t size)408 static void * ent_alloc(lv_mem_ent_t * e, uint32_t size)
409 {
410     void * alloc = NULL;
411 
412     /*If the memory is free and big enough then use it */
413     if(e->header.s.used == 0 && e->header.s.d_size >= size) {
414         /*Truncate the entry to the desired size */
415         ent_trunc(e, size),
416 
417             e->header.s.used = 1;
418 
419         /*Save the allocated data*/
420         alloc = &e->first_data;
421     }
422 
423     return alloc;
424 }
425 
426 /**
427  * Truncate the data of entry to the given size
428  * @param e Pointer to an entry
429  * @param size new size in bytes
430  */
ent_trunc(lv_mem_ent_t * e,uint32_t size)431 static void ent_trunc(lv_mem_ent_t * e, uint32_t size)
432 {
433 #ifdef LV_MEM_ENV64
434     /*Round the size up to 8*/
435     if(size & 0x7) {
436         size = size & (~0x7);
437         size += 8;
438     }
439 #else
440     /*Round the size up to 4*/
441     if(size & 0x3) {
442         size = size & (~0x3);
443         size += 4;
444     }
445 #endif
446 
447     /*Don't let empty space only for a header without data*/
448     if(e->header.s.d_size == size + sizeof(lv_mem_header_t)) {
449         size = e->header.s.d_size;
450     }
451 
452     /* Create the new entry after the current if there is space for it */
453     if(e->header.s.d_size != size) {
454         uint8_t * e_data             = &e->first_data;
455         lv_mem_ent_t * after_new_e   = (lv_mem_ent_t *)&e_data[size];
456         after_new_e->header.s.used   = 0;
457         after_new_e->header.s.d_size = e->header.s.d_size - size - sizeof(lv_mem_header_t);
458     }
459 
460     /* Set the new size for the original entry */
461     e->header.s.d_size = size;
462 }
463 
464 #endif
465