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