1 /*
2  * Copyright (c) 2021 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #ifndef ZEPHYR_INCLUDE_SYS_HEAP_LISTENER_H
8 #define ZEPHYR_INCLUDE_SYS_HEAP_LISTENER_H
9 
10 #include <stdint.h>
11 #include <zephyr/toolchain.h>
12 #include <zephyr/sys/slist.h>
13 
14 #ifdef __cplusplus
15 extern "C" {
16 #endif
17 
18 #if defined(CONFIG_HEAP_LISTENER) || defined(__DOXYGEN__)
19 
20 /**
21  * @defgroup heap_listener_apis Heap Listener APIs
22  * @ingroup heaps
23  * @{
24  */
25 
26 enum heap_event_types {
27 	/*
28 	 * Dummy event so an un-initialized but zero-ed listener node
29 	 * will not trigger any callbacks.
30 	 */
31 	HEAP_EVT_UNKNOWN = 0,
32 
33 	HEAP_RESIZE,
34 	HEAP_ALLOC,
35 	HEAP_FREE,
36 	HEAP_REALLOC,
37 
38 	HEAP_MAX_EVENTS
39 };
40 
41 /**
42  * @typedef heap_listener_resize_cb_t
43  * @brief Callback used when heap is resized
44  *
45  * @note Minimal C library does not emit this event.
46  *
47  * @param heap_id Identifier of heap being resized
48  * @param old_heap_end Pointer to end of heap before resize
49  * @param new_heap_end Pointer to end of heap after resize
50  */
51 typedef void (*heap_listener_resize_cb_t)(uintptr_t heap_id,
52 					  void *old_heap_end,
53 					  void *new_heap_end);
54 
55 /**
56  * @typedef heap_listener_alloc_cb_t
57  * @brief Callback used when there is heap allocation
58  *
59  * @note Heaps managed by libraries outside of code in
60  *       Zephyr main code repository may not emit this event.
61  *
62  * @note The number of bytes allocated may not match exactly
63  *       to the request to the allocation function. Internal
64  *       mechanism of the heap may allocate more than
65  *       requested.
66  *
67  * @param heap_id Heap identifier
68  * @param mem Pointer to the allocated memory
69  * @param bytes Size of allocated memory
70  */
71 typedef void (*heap_listener_alloc_cb_t)(uintptr_t heap_id,
72 					 void *mem, size_t bytes);
73 
74 /**
75  * @typedef heap_listener_free_cb_t
76  * @brief Callback used when memory is freed from heap
77  *
78  * @note Heaps managed by libraries outside of code in
79  *       Zephyr main code repository may not emit this event.
80  *
81  * @note The number of bytes freed may not match exactly to
82  *       the request to the allocation function. Internal
83  *       mechanism of the heap dictates how memory is
84  *       allocated or freed.
85  *
86  * @param heap_id Heap identifier
87  * @param mem Pointer to the freed memory
88  * @param bytes Size of freed memory
89  */
90 typedef void (*heap_listener_free_cb_t)(uintptr_t heap_id,
91 					void *mem, size_t bytes);
92 
93 struct heap_listener {
94 	/** Singly linked list node */
95 	sys_snode_t node;
96 
97 	/**
98 	 * Identifier of the heap whose events are listened.
99 	 *
100 	 * It can be a heap pointer, if the heap is represented as an object,
101 	 * or 0 in the case of the global libc heap.
102 	 */
103 	uintptr_t heap_id;
104 
105 	/**
106 	 * The heap event to be notified.
107 	 */
108 	enum heap_event_types event;
109 
110 	union {
111 		heap_listener_alloc_cb_t alloc_cb;
112 		heap_listener_free_cb_t free_cb;
113 		heap_listener_resize_cb_t resize_cb;
114 	};
115 };
116 
117 /**
118  * @brief Register heap event listener
119  *
120  * Add the listener to the global list of heap listeners that can be notified by
121  * different heap implementations upon certain events related to the heap usage.
122  *
123  * @param listener Pointer to the heap_listener object
124  */
125 void heap_listener_register(struct heap_listener *listener);
126 
127 /**
128  * @brief Unregister heap event listener
129  *
130  * Remove the listener from the global list of heap listeners that can be
131  * notified by different heap implementations upon certain events related to the
132  * heap usage.
133  *
134  * @param listener Pointer to the heap_listener object
135  */
136 void heap_listener_unregister(struct heap_listener *listener);
137 
138 /**
139  * @brief Notify listeners of heap allocation event
140  *
141  * Notify registered heap event listeners with matching heap identifier that an
142  * allocation has been done on heap
143  *
144  * @param heap_id Heap identifier
145  * @param mem Pointer to the allocated memory
146  * @param bytes Size of allocated memory
147  */
148 void heap_listener_notify_alloc(uintptr_t heap_id, void *mem, size_t bytes);
149 
150 /**
151  * @brief Notify listeners of heap free event
152  *
153  * Notify registered heap event listeners with matching heap identifier that
154  * memory is freed on heap
155  *
156  * @param heap_id Heap identifier
157  * @param mem Pointer to the freed memory
158  * @param bytes Size of freed memory
159  */
160 void heap_listener_notify_free(uintptr_t heap_id, void *mem, size_t bytes);
161 
162 /**
163  * @brief Notify listeners of heap resize event
164  *
165  * Notify registered heap event listeners with matching heap identifier that the
166  * heap has been resized.
167  *
168  * @param heap_id Heap identifier
169  * @param old_heap_end Address of the heap end before the change
170  * @param new_heap_end Address of the heap end after the change
171  */
172 void heap_listener_notify_resize(uintptr_t heap_id, void *old_heap_end, void *new_heap_end);
173 
174 /**
175  * @brief Construct heap identifier from heap pointer
176  *
177  * Construct a heap identifier from a pointer to the heap object, such as
178  * sys_heap.
179  *
180  * @param heap_pointer Pointer to the heap object
181  */
182 #define HEAP_ID_FROM_POINTER(heap_pointer) ((uintptr_t)heap_pointer)
183 
184 /**
185  * @brief Libc heap identifier
186  *
187  * Identifier of the global libc heap.
188  */
189 #define HEAP_ID_LIBC ((uintptr_t)0)
190 
191 /**
192  * @brief Define heap event listener node for allocation event
193  *
194  * Sample usage:
195  * @code
196  * void on_heap_alloc(uintptr_t heap_id, void *mem, size_t bytes)
197  * {
198  *   LOG_INF("Memory allocated at %p, size %ld", mem, bytes);
199  * }
200  *
201  * HEAP_LISTENER_ALLOC_DEFINE(my_listener, HEAP_ID_LIBC, on_heap_alloc);
202  * @endcode
203  *
204  * @param name		Name of the heap event listener object
205  * @param _heap_id	Identifier of the heap to be listened
206  * @param _alloc_cb	Function to be called for allocation event
207  */
208 #define HEAP_LISTENER_ALLOC_DEFINE(name, _heap_id, _alloc_cb) \
209 	struct heap_listener name = { \
210 		.heap_id = _heap_id, \
211 		.event = HEAP_ALLOC, \
212 		{ \
213 			.alloc_cb = _alloc_cb \
214 		}, \
215 	}
216 
217 /**
218  * @brief Define heap event listener node for free event
219  *
220  * Sample usage:
221  * @code
222  * void on_heap_free(uintptr_t heap_id, void *mem, size_t bytes)
223  * {
224  *   LOG_INF("Memory freed at %p, size %ld", mem, bytes);
225  * }
226  *
227  * HEAP_LISTENER_FREE_DEFINE(my_listener, HEAP_ID_LIBC, on_heap_free);
228  * @endcode
229  *
230  * @param name		Name of the heap event listener object
231  * @param _heap_id	Identifier of the heap to be listened
232  * @param _free_cb	Function to be called for free event
233  */
234 #define HEAP_LISTENER_FREE_DEFINE(name, _heap_id, _free_cb) \
235 	struct heap_listener name = { \
236 		.heap_id = _heap_id, \
237 		.event = HEAP_FREE, \
238 		{ \
239 			.free_cb = _free_cb \
240 		}, \
241 	}
242 
243 /**
244  * @brief Define heap event listener node for resize event
245  *
246  * Sample usage:
247  * @code
248  * void on_heap_resized(uintptr_t heap_id, void *old_heap_end, void *new_heap_end)
249  * {
250  *   LOG_INF("Libc heap end moved from %p to %p", old_heap_end, new_heap_end);
251  * }
252  *
253  * HEAP_LISTENER_RESIZE_DEFINE(my_listener, HEAP_ID_LIBC, on_heap_resized);
254  * @endcode
255  *
256  * @param name		Name of the heap event listener object
257  * @param _heap_id	Identifier of the heap to be listened
258  * @param _resize_cb	Function to be called when the listened heap is resized
259  */
260 #define HEAP_LISTENER_RESIZE_DEFINE(name, _heap_id, _resize_cb) \
261 	struct heap_listener name = { \
262 		.heap_id = _heap_id, \
263 		.event = HEAP_RESIZE, \
264 		{ \
265 			.resize_cb = _resize_cb \
266 		}, \
267 	}
268 
269 /** @} */
270 
271 #else /* CONFIG_HEAP_LISTENER */
272 
273 #define HEAP_ID_FROM_POINTER(heap_pointer) ((uintptr_t)NULL)
274 
275 static inline void heap_listener_notify_alloc(uintptr_t heap_id, void *mem, size_t bytes)
276 {
277 	ARG_UNUSED(heap_id);
278 	ARG_UNUSED(mem);
279 	ARG_UNUSED(bytes);
280 }
281 
282 static inline void heap_listener_notify_free(uintptr_t heap_id, void *mem, size_t bytes)
283 {
284 	ARG_UNUSED(heap_id);
285 	ARG_UNUSED(mem);
286 	ARG_UNUSED(bytes);
287 }
288 
289 static inline void heap_listener_notify_resize(uintptr_t heap_id, void *old_heap_end,
290 					       void *new_heap_end)
291 {
292 	ARG_UNUSED(heap_id);
293 	ARG_UNUSED(old_heap_end);
294 	ARG_UNUSED(new_heap_end);
295 }
296 
297 #endif /* CONFIG_HEAP_LISTENER */
298 
299 #ifdef __cplusplus
300 }
301 #endif
302 
303 #endif /* ZEPHYR_INCLUDE_SYS_HEAP_LISTENER_H */
304