1 /*
2 * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /*
8 * This file is a patch for the tlsf implementation stored in ROM
9 * - tlsf_check() now implements a call to a hook giving the user the possibility
10 * to implement specific checks on the memory of every free blocks.
11 * - The function tlsf_poison_check_pfunc_set() was added to allow the user to
12 * register the hook function called in tlsf_check().
13 */
14
15 #include <stddef.h>
16 #include <stdbool.h>
17 #include <string.h>
18
19 #include "esp_rom_caps.h"
20 #include "esp_rom_tlsf.h"
21
22 /*!
23 * @brief Opaque types for TLSF implementation
24 */
25 typedef void* tlsf_t;
26 typedef void* pool_t;
27 typedef void* tlsf_walker;
28
29 /* ----------------------------------------------------------------
30 * Bring certain inline functions, macro and structures from the
31 * tlsf ROM implementation to be able to compile the patch.
32 * ---------------------------------------------------------------- */
33
34 #define tlsf_cast(t, exp) ((t) (exp))
35
36 #define block_header_free_bit (1 << 0)
37 #define block_header_prev_free_bit (1 << 1)
38 #define block_header_overhead (sizeof(size_t))
39 #define block_start_offset (offsetof(block_header_t, size) + sizeof(size_t))
40
41 typedef struct block_header_t
42 {
43 /* Points to the previous physical block. */
44 struct block_header_t* prev_phys_block;
45
46 /* The size of this block, excluding the block header. */
47 size_t size;
48
49 /* Next and previous free blocks. */
50 struct block_header_t* next_free;
51 struct block_header_t* prev_free;
52 } block_header_t;
53
block_size(const block_header_t * block)54 static inline __attribute__((__always_inline__)) size_t block_size(const block_header_t* block)
55 {
56 return block->size & ~(block_header_free_bit | block_header_prev_free_bit);
57 }
58
block_is_free(const block_header_t * block)59 static inline __attribute__((__always_inline__)) int block_is_free(const block_header_t* block)
60 {
61 return tlsf_cast(int, block->size & block_header_free_bit);
62 }
63
block_is_prev_free(const block_header_t * block)64 static inline __attribute__((__always_inline__)) int block_is_prev_free(const block_header_t* block)
65 {
66 return tlsf_cast(int, block->size & block_header_prev_free_bit);
67 }
68
block_from_ptr(const void * ptr)69 static inline __attribute__((always_inline)) block_header_t* block_from_ptr(const void* ptr)
70 {
71 return tlsf_cast(block_header_t*,
72 tlsf_cast(unsigned char*, ptr) - block_start_offset);
73 }
74
75 /* ----------------------------------------------------------------
76 * End of the environment necessary to compile and link the patch
77 * defined below
78 * ---------------------------------------------------------------- */
79
80 static poison_check_pfunc_t s_poison_check_region = NULL;
81
tlsf_poison_check_pfunc_set(poison_check_pfunc_t pfunc)82 void tlsf_poison_check_pfunc_set(poison_check_pfunc_t pfunc)
83 {
84 s_poison_check_region = pfunc;
85 }
86
87 #define tlsf_insist_no_assert(x) { if (!(x)) { status--; } }
88
89 typedef struct integrity_t
90 {
91 int prev_status;
92 int status;
93 } integrity_t;
94
integrity_walker(void * ptr,size_t size,int used,void * user)95 static void integrity_walker(void* ptr, size_t size, int used, void* user)
96 {
97 block_header_t* block = block_from_ptr(ptr);
98 integrity_t* integ = tlsf_cast(integrity_t*, user);
99 const int this_prev_status = block_is_prev_free(block) ? 1 : 0;
100 const int this_status = block_is_free(block) ? 1 : 0;
101 const size_t this_block_size = block_size(block);
102
103 int status = 0;
104 tlsf_insist_no_assert(integ->prev_status == this_prev_status && "prev status incorrect");
105 tlsf_insist_no_assert(size == this_block_size && "block size incorrect");
106
107 if (s_poison_check_region != NULL)
108 {
109 /* block_size(block) returns the size of the usable memory when the block is allocated.
110 * As the block under test is free, we need to subtract to the block size the next_free
111 * and prev_free fields of the block header as they are not a part of the usable memory
112 * when the block is free. In addition, we also need to subtract the size of prev_phys_block
113 * as this field is in fact part of the current free block and not part of the next (allocated)
114 * block. Check the comments in block_split function for more details.
115 */
116 const size_t actual_free_block_size = used ? this_block_size :
117 this_block_size - offsetof(block_header_t, next_free)- block_header_overhead;
118
119 void* ptr_block = used ? (void*)block + block_start_offset :
120 (void*)block + sizeof(block_header_t);
121
122 tlsf_insist_no_assert(s_poison_check_region(ptr_block, actual_free_block_size, !used, true));
123 }
124
125 integ->prev_status = this_status;
126 integ->status += status;
127 }
128
129 extern void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
tlsf_check_pool(pool_t pool)130 int tlsf_check_pool(pool_t pool)
131 {
132 /* Check that the blocks are physically correct. */
133 integrity_t integ = { 0, 0 };
134 tlsf_walk_pool(pool, integrity_walker, &integ);
135
136 return integ.status;
137 }
138
139 #undef tlsf_insist_no_assert
140
141 /* Set up the TLSF ROM patches here */
142
143 /*!
144 * @brief Structure to store all the functions of a TLSF implementation.
145 * The goal of this table is to change any of the address here in order
146 * to let the ROM code call another function implementation than the one
147 * in ROM.
148 */
149 struct heap_tlsf_stub_table_t {
150 tlsf_t (*tlsf_create)(void* mem);
151 tlsf_t (*tlsf_create_with_pool)(void* mem, size_t bytes);
152 pool_t (*tlsf_get_pool)(tlsf_t tlsf);
153 pool_t (*tlsf_add_pool)(tlsf_t tlsf, void* mem, size_t bytes);
154 void (*tlsf_remove_pool)(tlsf_t tlsf, pool_t pool);
155
156 void* (*tlsf_malloc)(tlsf_t tlsf, size_t size);
157 void* (*tlsf_memalign)(tlsf_t tlsf, size_t align, size_t size);
158 void* (*tlsf_memalign_offs)(tlsf_t tlsf, size_t align, size_t size, size_t offset);
159 void* (*tlsf_realloc)(tlsf_t tlsf, void* ptr, size_t size);
160 void (*tlsf_free)(tlsf_t tlsf, void* ptr);
161
162 size_t (*tlsf_block_size)(void* ptr);
163 size_t (*tlsf_size)(void);
164 size_t (*tlsf_align_size)(void);
165 size_t (*tlsf_block_size_min)(void);
166 size_t (*tlsf_block_size_max)(void);
167 size_t (*tlsf_pool_overhead)(void);
168 size_t (*tlsf_alloc_overhead)(void);
169
170 void (*tlsf_walk_pool)(pool_t pool, tlsf_walker walker, void* user);
171
172 int (*tlsf_check)(tlsf_t tlsf);
173 int (*tlsf_check_pool)(pool_t pool);
174 };
175
176 /* We need the original table from the ROM */
177 extern struct heap_tlsf_stub_table_t* heap_tlsf_table_ptr;
178
179 /* We will copy the ROM table and modify the functions we patch */
180 struct heap_tlsf_stub_table_t heap_tlsf_patch_table_ptr;
181
182 /*!
183 * @brief Setup the TLSF ROM patches.
184 * This function must be called when setting up the heap. It will put in place the function patched
185 * from the ROM implementation.
186 * This function must not be defined as static, as it is marked as "undefined" in the linker flags
187 * (to force the linker to integrate the functions of this file inside the final binary)
188 */
tlsf_set_rom_patches(void)189 void __attribute__((constructor)) tlsf_set_rom_patches(void)
190 {
191 /* Copy the ROM default table inside our table */
192 memcpy(&heap_tlsf_patch_table_ptr, heap_tlsf_table_ptr, sizeof(struct heap_tlsf_stub_table_t));
193
194 /* Set the patched function here */
195 heap_tlsf_patch_table_ptr.tlsf_check_pool = tlsf_check_pool;
196
197 /* Set our table as the one to use in the ROM code */
198 heap_tlsf_table_ptr = &heap_tlsf_patch_table_ptr;
199 }
200