1 /*
2  * Copyright (c) 2006-2022, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2006-05-27     Bernard      implement memory pool
9  * 2006-06-03     Bernard      fix the thread timer init bug
10  * 2006-06-30     Bernard      fix the allocate/free block bug
11  * 2006-08-04     Bernard      add hook support
12  * 2006-08-10     Bernard      fix interrupt bug in rt_mp_alloc
13  * 2010-07-13     Bernard      fix RT_ALIGN issue found by kuronca
14  * 2010-10-26     yi.qiu       add module support in rt_mp_delete
15  * 2011-01-24     Bernard      add object allocation check.
16  * 2012-03-22     Bernard      fix align issue in rt_mp_init and rt_mp_create.
17  * 2022-01-07     Gabriel      Moving __on_rt_xxxxx_hook to mempool.c
18  * 2023-09-15     xqyjlj       perf rt_hw_interrupt_disable/enable
19  * 2023-12-10     xqyjlj       fix spinlock assert
20  */
21 
22 #include <rthw.h>
23 #include <rtthread.h>
24 
25 #ifdef RT_USING_MEMPOOL
26 
27 #if defined(RT_USING_HOOK) && defined(RT_HOOK_USING_FUNC_PTR)
28 static void (*rt_mp_alloc_hook)(struct rt_mempool *mp, void *block);
29 static void (*rt_mp_free_hook)(struct rt_mempool *mp, void *block);
30 
31 /**
32  * @addtogroup group_hook
33  */
34 
35 /**@{*/
36 
37 /**
38  * @brief This function will set a hook function, which will be invoked when a memory
39  *        block is allocated from the memory pool.
40  *
41  * @param hook the hook function
42  */
rt_mp_alloc_sethook(void (* hook)(struct rt_mempool * mp,void * block))43 void rt_mp_alloc_sethook(void (*hook)(struct rt_mempool *mp, void *block))
44 {
45     rt_mp_alloc_hook = hook;
46 }
47 
48 /**
49  * @brief This function will set a hook function, which will be invoked when a memory
50  *        block is released to the memory pool.
51  *
52  * @param hook the hook function
53  */
rt_mp_free_sethook(void (* hook)(struct rt_mempool * mp,void * block))54 void rt_mp_free_sethook(void (*hook)(struct rt_mempool *mp, void *block))
55 {
56     rt_mp_free_hook = hook;
57 }
58 
59 /**@}*/
60 #endif /* RT_USING_HOOK */
61 
62 /**
63  * @addtogroup group_memory_management
64  */
65 
66 /**@{*/
67 
68 /**
69  * @brief  This function will initialize a memory pool object, normally which is used
70  *         for static object.
71  *
72  * @param  mp is the memory pool object.
73  *
74  * @param  name is the name of the memory pool.
75  *
76  * @param  start is the start address of the memory pool.
77  *
78  * @param  size is the total size of the memory pool.
79  *
80  * @param  block_size is the size for each block..
81  *
82  * @return RT_EOK
83  */
rt_mp_init(struct rt_mempool * mp,const char * name,void * start,rt_size_t size,rt_size_t block_size)84 rt_err_t rt_mp_init(struct rt_mempool *mp,
85                     const char        *name,
86                     void              *start,
87                     rt_size_t          size,
88                     rt_size_t          block_size)
89 {
90     rt_uint8_t *block_ptr;
91     rt_size_t offset;
92 
93     /* parameter check */
94     RT_ASSERT(mp != RT_NULL);
95     RT_ASSERT(name != RT_NULL);
96     RT_ASSERT(start != RT_NULL);
97     RT_ASSERT(size > 0 && block_size > 0);
98 
99     /* initialize object */
100     rt_object_init(&(mp->parent), RT_Object_Class_MemPool, name);
101 
102     /* initialize memory pool */
103     mp->start_address = start;
104     mp->size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
105 
106     /* align the block size */
107     block_size = RT_ALIGN(block_size, RT_ALIGN_SIZE);
108     mp->block_size = block_size;
109 
110     /* align to align size byte */
111     mp->block_total_count = mp->size / (mp->block_size + sizeof(rt_uint8_t *));
112     mp->block_free_count  = mp->block_total_count;
113 
114     /* initialize suspended thread list */
115     rt_list_init(&(mp->suspend_thread));
116 
117     /* initialize free block list */
118     block_ptr = (rt_uint8_t *)mp->start_address;
119     for (offset = 0; offset < mp->block_total_count; offset ++)
120     {
121         *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *))) =
122             (rt_uint8_t *)(block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *)));
123     }
124 
125     *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *))) =
126         RT_NULL;
127 
128     mp->block_list = block_ptr;
129     rt_spin_lock_init(&(mp->spinlock));
130 
131     return RT_EOK;
132 }
133 RTM_EXPORT(rt_mp_init);
134 
135 /**
136  * @brief  This function will detach a memory pool from system object management.
137  *
138  * @param  mp is the memory pool object.
139  *
140  * @return RT_EOK
141  */
rt_mp_detach(struct rt_mempool * mp)142 rt_err_t rt_mp_detach(struct rt_mempool *mp)
143 {
144     rt_base_t level;
145 
146     /* parameter check */
147     RT_ASSERT(mp != RT_NULL);
148     RT_ASSERT(rt_object_get_type(&mp->parent) == RT_Object_Class_MemPool);
149     RT_ASSERT(rt_object_is_systemobject(&mp->parent));
150 
151     level = rt_spin_lock_irqsave(&(mp->spinlock));
152     /* wake up all suspended threads */
153     rt_susp_list_resume_all(&mp->suspend_thread, RT_ERROR);
154 
155     /* detach object */
156     rt_object_detach(&(mp->parent));
157     rt_spin_unlock_irqrestore(&(mp->spinlock), level);
158 
159     return RT_EOK;
160 }
161 RTM_EXPORT(rt_mp_detach);
162 
163 #ifdef RT_USING_HEAP
164 /**
165  * @brief This function will create a mempool object and allocate the memory pool from
166  *        heap.
167  *
168  * @param name is the name of memory pool.
169  *
170  * @param block_count is the count of blocks in memory pool.
171  *
172  * @param block_size is the size for each block.
173  *
174  * @return the created mempool object
175  */
rt_mp_create(const char * name,rt_size_t block_count,rt_size_t block_size)176 rt_mp_t rt_mp_create(const char *name,
177                      rt_size_t   block_count,
178                      rt_size_t   block_size)
179 {
180     rt_uint8_t *block_ptr;
181     struct rt_mempool *mp;
182     rt_size_t offset;
183 
184     RT_DEBUG_NOT_IN_INTERRUPT;
185 
186     /* parameter check */
187     RT_ASSERT(name != RT_NULL);
188     RT_ASSERT(block_count > 0 && block_size > 0);
189 
190     /* allocate object */
191     mp = (struct rt_mempool *)rt_object_allocate(RT_Object_Class_MemPool, name);
192     /* allocate object failed */
193     if (mp == RT_NULL)
194         return RT_NULL;
195 
196     /* initialize memory pool */
197     block_size     = RT_ALIGN(block_size, RT_ALIGN_SIZE);
198     mp->block_size = block_size;
199     mp->size       = (block_size + sizeof(rt_uint8_t *)) * block_count;
200 
201     /* allocate memory */
202     mp->start_address = rt_malloc((block_size + sizeof(rt_uint8_t *)) *
203                                   block_count);
204     if (mp->start_address == RT_NULL)
205     {
206         /* no memory, delete memory pool object */
207         rt_object_delete(&(mp->parent));
208 
209         return RT_NULL;
210     }
211 
212     mp->block_total_count = block_count;
213     mp->block_free_count  = mp->block_total_count;
214 
215     /* initialize suspended thread list */
216     rt_list_init(&(mp->suspend_thread));
217 
218     /* initialize free block list */
219     block_ptr = (rt_uint8_t *)mp->start_address;
220     for (offset = 0; offset < mp->block_total_count; offset ++)
221     {
222         *(rt_uint8_t **)(block_ptr + offset * (block_size + sizeof(rt_uint8_t *)))
223             = block_ptr + (offset + 1) * (block_size + sizeof(rt_uint8_t *));
224     }
225 
226     *(rt_uint8_t **)(block_ptr + (offset - 1) * (block_size + sizeof(rt_uint8_t *)))
227         = RT_NULL;
228 
229     mp->block_list = block_ptr;
230     rt_spin_lock_init(&(mp->spinlock));
231 
232     return mp;
233 }
234 RTM_EXPORT(rt_mp_create);
235 
236 /**
237  * @brief This function will delete a memory pool and release the object memory.
238  *
239  * @param mp is the memory pool object.
240  *
241  * @return RT_EOK
242  */
rt_mp_delete(rt_mp_t mp)243 rt_err_t rt_mp_delete(rt_mp_t mp)
244 {
245     rt_base_t level;
246 
247     RT_DEBUG_NOT_IN_INTERRUPT;
248 
249     /* parameter check */
250     RT_ASSERT(mp != RT_NULL);
251     RT_ASSERT(rt_object_get_type(&mp->parent) == RT_Object_Class_MemPool);
252     RT_ASSERT(rt_object_is_systemobject(&mp->parent) == RT_FALSE);
253 
254     level = rt_spin_lock_irqsave(&(mp->spinlock));
255     /* wake up all suspended threads */
256     rt_susp_list_resume_all(&mp->suspend_thread, RT_ERROR);
257 
258     rt_spin_unlock_irqrestore(&(mp->spinlock), level);
259 
260     /* release allocated room */
261     rt_free(mp->start_address);
262 
263     /* detach object */
264     rt_object_delete(&(mp->parent));
265 
266     return RT_EOK;
267 }
268 RTM_EXPORT(rt_mp_delete);
269 #endif /* RT_USING_HEAP */
270 
271 /**
272  * @brief This function will allocate a block from memory pool.
273  *
274  * @param mp is the memory pool object.
275  *
276  * @param time is the maximum waiting time for allocating memory.
277  *             - 0 for not waiting, allocating memory immediately.
278  *
279  * @return the allocated memory block or RT_NULL on allocated failed.
280  */
rt_mp_alloc(rt_mp_t mp,rt_int32_t time)281 void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)
282 {
283     rt_uint8_t *block_ptr;
284     rt_base_t level;
285     struct rt_thread *thread;
286     rt_uint32_t before_sleep = 0;
287 
288     /* parameter check */
289     RT_ASSERT(mp != RT_NULL);
290 
291     /* get current thread */
292     thread = rt_thread_self();
293 
294     level = rt_spin_lock_irqsave(&(mp->spinlock));
295 
296     while (mp->block_free_count == 0)
297     {
298         /* memory block is unavailable. */
299         if (time == 0)
300         {
301             rt_spin_unlock_irqrestore(&(mp->spinlock), level);
302 
303             rt_set_errno(-RT_ETIMEOUT);
304 
305             return RT_NULL;
306         }
307 
308         RT_DEBUG_NOT_IN_INTERRUPT;
309 
310         thread->error = RT_EOK;
311 
312         /* need suspend thread */
313         rt_thread_suspend_to_list(thread, &mp->suspend_thread, RT_IPC_FLAG_FIFO, RT_UNINTERRUPTIBLE);
314 
315         if (time > 0)
316         {
317             /* get the start tick of timer */
318             before_sleep = rt_tick_get();
319 
320             /* init thread timer and start it */
321             rt_timer_control(&(thread->thread_timer),
322                              RT_TIMER_CTRL_SET_TIME,
323                              &time);
324             rt_timer_start(&(thread->thread_timer));
325         }
326 
327         /* enable interrupt */
328         rt_spin_unlock_irqrestore(&(mp->spinlock), level);
329 
330         /* do a schedule */
331         rt_schedule();
332 
333         if (thread->error != RT_EOK)
334             return RT_NULL;
335 
336         if (time > 0)
337         {
338             time -= rt_tick_get() - before_sleep;
339             if (time < 0)
340                 time = 0;
341         }
342         level = rt_spin_lock_irqsave(&(mp->spinlock));
343     }
344 
345     /* memory block is available. decrease the free block counter */
346     mp->block_free_count--;
347 
348     /* get block from block list */
349     block_ptr = mp->block_list;
350     RT_ASSERT(block_ptr != RT_NULL);
351 
352     /* Setup the next free node. */
353     mp->block_list = *(rt_uint8_t **)block_ptr;
354 
355     /* point to memory pool */
356     *(rt_uint8_t **)block_ptr = (rt_uint8_t *)mp;
357 
358     rt_spin_unlock_irqrestore(&(mp->spinlock), level);
359 
360     RT_OBJECT_HOOK_CALL(rt_mp_alloc_hook,
361                         (mp, (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *))));
362 
363     return (rt_uint8_t *)(block_ptr + sizeof(rt_uint8_t *));
364 }
365 RTM_EXPORT(rt_mp_alloc);
366 
367 /**
368  * @brief This function will release a memory block.
369  *
370  * @param block the address of memory block to be released.
371  */
rt_mp_free(void * block)372 void rt_mp_free(void *block)
373 {
374     rt_uint8_t **block_ptr;
375     struct rt_mempool *mp;
376     rt_base_t level;
377 
378     /* parameter check */
379     if (block == RT_NULL) return;
380 
381     /* get the control block of pool which the block belongs to */
382     block_ptr = (rt_uint8_t **)((rt_uint8_t *)block - sizeof(rt_uint8_t *));
383     mp        = (struct rt_mempool *)*block_ptr;
384 
385     RT_OBJECT_HOOK_CALL(rt_mp_free_hook, (mp, block));
386 
387     level = rt_spin_lock_irqsave(&(mp->spinlock));
388 
389     /* increase the free block count */
390     mp->block_free_count ++;
391 
392     /* link the block into the block list */
393     *block_ptr = mp->block_list;
394     mp->block_list = (rt_uint8_t *)block_ptr;
395 
396     if (rt_susp_list_dequeue(&mp->suspend_thread, RT_EOK))
397     {
398         rt_spin_unlock_irqrestore(&(mp->spinlock), level);
399 
400         /* do a schedule */
401         rt_schedule();
402 
403         return;
404     }
405     rt_spin_unlock_irqrestore(&(mp->spinlock), level);
406 }
407 RTM_EXPORT(rt_mp_free);
408 
409 /**@}*/
410 
411 #endif /* RT_USING_MEMPOOL */
412