qpc/source/qf_mem.c
Quantum Leaps ed7ef157e9 5.9.7b
2017-09-01 16:30:05 -04:00

303 lines
12 KiB
C

/**
* @file
* @ingroup qf
* @brief ::QMPool implementatin (Memory Pool)
* @cond
******************************************************************************
* Last updated for version 5.5.0
* Last updated on 2015-08-20
*
* Q u a n t u m L e a P s
* ---------------------------
* innovating embedded systems
*
* Copyright (C) Quantum Leaps, www.state-machine.com.
*
* This program is open source software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alternatively, this program may be distributed and modified under the
* terms of Quantum Leaps commercial licenses, which expressly supersede
* the GNU General Public License and are specifically designed for
* licensees interested in retaining the proprietary status of their code.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Contact information:
* Web: www.state-machine.com
* Email: info@state-machine.com
******************************************************************************
* @endcond
*/
#define QP_IMPL /* this is QP implementation */
#include "qf_port.h" /* QF port */
#include "qf_pkg.h" /* QF package-scope interface */
#include "qassert.h" /* QP embedded systems-friendly assertions */
#ifdef Q_SPY /* QS software tracing enabled? */
#include "qs_port.h" /* include QS port */
#else
#include "qs_dummy.h" /* disable the QS software tracing */
#endif /* Q_SPY */
Q_DEFINE_THIS_MODULE("qf_mem")
/****************************************************************************/
/**
* @description
* Initialize a fixed block-size memory pool by providing it with the pool
* memory to manage, size of this memory, and the block size.
*
* @param[in,out] me pointer (see @ref oop)
* @param[in] poolSto pointer to the memory buffer for pool storage
* @param[in] poolSize size of the storage buffer in bytes
* @param[in] blockSize fixed-size of the memory blocks in bytes
*
* @attention
* The caller of QMPool::init() must make sure that the @p poolSto
* pointer is properly **aligned**. In particular, it must be possible to
* efficiently store a pointer at the location pointed to by @p poolSto.
* Internally, the QMPool_init() function rounds up the block size
* @p blockSize so that it can fit an integer number of pointers.
* This is done to achieve proper alignment of the blocks within the pool.
*
* @note Due to the rounding of block size the actual capacity of the pool
* might be less than (@p poolSize / @p blockSize). You can check the capacity
* of the pool by calling the QF_getPoolMin() function.
*
* @note This function is **not** protected by a critical section, because
* it is intended to be called only during the initialization of the system,
* when interrupts are not allowed yet.
*
* @note Many QF ports use memory pools to implement the event pools.
*
* @usage
* The following example illustrates how to invoke QMPool_init():
* @include qmp_init.c
*/
void QMPool_init(QMPool * const me, void * const poolSto,
uint_fast32_t poolSize, uint_fast16_t blockSize)
{
QFreeBlock *fb;
uint_fast16_t nblocks;
QS_CRIT_STAT_
/** @pre The memory block must be valid
* and the poolSize must fit at least one free block
* and the blockSize must not be too close to the top of the dynamic range
*/
Q_REQUIRE_ID(100, (poolSto != (void *)0)
&& (poolSize >= (uint_fast32_t)sizeof(QFreeBlock))
&& ((uint_fast16_t)(blockSize
+ (uint_fast16_t)sizeof(QFreeBlock)) > blockSize));
me->free_head = poolSto;
/* round up the blockSize to fit an integer # free blocks, no division */
me->blockSize = (QMPoolSize)sizeof(QFreeBlock); /* start with just one */
nblocks = (uint_fast16_t)1;/* #free blocks that fit in one memory block */
while (me->blockSize < (QMPoolSize)blockSize) {
me->blockSize += (QMPoolSize)sizeof(QFreeBlock);
++nblocks;
}
blockSize = (uint_fast16_t)me->blockSize; /* round-up to nearest block */
/* the pool buffer must fit at least one rounded-up block */
Q_ASSERT_ID(110, poolSize >= (uint_fast32_t)blockSize);
/* chain all blocks together in a free-list... */
poolSize -= (uint_fast32_t)blockSize; /* don't count the last block */
me->nTot = (QMPoolCtr)1; /* the last block already in the pool */
fb = (QFreeBlock *)me->free_head; /* start at the head of the free list */
/* chain all blocks together in a free-list... */
while (poolSize >= (uint_fast32_t)blockSize) {
fb->next = &QF_PTR_AT_(fb, nblocks);/*point next link to next block */
fb = fb->next; /* advance to the next block */
poolSize -= (uint_fast32_t)blockSize; /* reduce available pool size */
++me->nTot; /* increment the number of blocks so far */
}
fb->next = (QFreeBlock *)0; /* the last link points to NULL */
me->nFree = me->nTot; /* all blocks are free */
me->nMin = me->nTot; /* the minimum number of free blocks */
me->start = poolSto; /* the original start this pool buffer */
me->end = fb; /* the last block in this pool */
QS_BEGIN_(QS_QF_MPOOL_INIT, QS_priv_.locFilter[MP_OBJ], me->start)
QS_OBJ_(me->start); /* the memory managed by this pool */
QS_MPC_(me->nTot); /* the total number of blocks */
QS_END_()
}
/****************************************************************************/
/**
* @description
* Recycle a memory block to the fixed block-size memory pool.
*
* @param[in,out] me pointer (see @ref oop)
* @param[in] b pointer to the memory block that is being recycled
*
* @attention
* The recycled block must be allocated from the **same** memory pool
* to which it is returned.
*
* @note This function can be called from any task level or ISR level.
*
* @sa QMPool_get()
*
* @usage
* The following example illustrates how to use QMPool_put():
* @include qmp_use.c
*/
void QMPool_put(QMPool * const me, void *b) {
QF_CRIT_STAT_
/** @pre # free blocks cannot exceed the total # blocks and
* the block pointer must be from this pool.
*/
Q_REQUIRE_ID(200, (me->nFree < me->nTot)
&& QF_PTR_RANGE_(b, me->start, me->end));
QF_CRIT_ENTRY_();
((QFreeBlock *)b)->next = (QFreeBlock *)me->free_head;/* link into list */
me->free_head = b; /* set as new head of the free list */
++me->nFree; /* one more free block in this pool */
QS_BEGIN_NOCRIT_(QS_QF_MPOOL_PUT, QS_priv_.locFilter[MP_OBJ], me->start)
QS_TIME_(); /* timestamp */
QS_OBJ_(me->start); /* the memory managed by this pool */
QS_MPC_(me->nFree); /* the number of free blocks in the pool */
QS_END_NOCRIT_()
QF_CRIT_EXIT_();
}
/****************************************************************************/
/**
* @description
* The function allocates a memory block from the pool and returns a pointer
* to the block back to the caller.
*
* @param[in,out] me pointer (see @ref oop)
* @param[in] margin the minimum number of unused blocks still available
* in the pool after the allocation.
*
* @note This function can be called from any task level or ISR level.
*
* @note The memory pool @p me must be initialized before any events can
* be requested from it. Also, the QMPool_get() function uses internally a
* QF critical section, so you should be careful not to call it from within
* a critical section when nesting of critical section is not supported.
*
* @attention
* An allocated block must be later returned back to the same pool
* from which it has been allocated.
*
* @sa QMPool_put()
*
* @usage
* The following example illustrates how to use QMPool_get():
* @include qmp_use.c
*/
void *QMPool_get(QMPool * const me, uint_fast16_t const margin) {
QFreeBlock *fb;
QF_CRIT_STAT_
QF_CRIT_ENTRY_();
/* have more free blocks than the requested margin? */
if (me->nFree > (QMPoolCtr)margin) {
void *fb_next;
fb = (QFreeBlock *)me->free_head; /* get a free block */
/* the pool has some free blocks, so a free block must be available */
Q_ASSERT_ID(310, fb != (QFreeBlock *)0);
fb_next = fb->next; /* put volatile to a temporary to avoid UB */
/* is the pool becoming empty? */
--me->nFree; /* one less free block */
if (me->nFree == (QMPoolCtr)0) {
/* pool is becoming empty, so the next free block must be NULL */
Q_ASSERT_ID(320, fb_next == (QFreeBlock *)0);
me->nMin = (QMPoolCtr)0; /* remember that the pool got empty */
}
else {
/* pool is not empty, so the next free block must be in range
*
* NOTE: the next free block pointer can fall out of range
* when the client code writes past the memory block, thus
* corrupting the next block.
*/
Q_ASSERT_ID(330, QF_PTR_RANGE_(fb_next, me->start, me->end));
/* is the number of free blocks the new minimum so far? */
if (me->nMin > me->nFree) {
me->nMin = me->nFree; /* remember the new minimum */
}
}
me->free_head = fb_next; /* set the head to the next free block */
QS_BEGIN_NOCRIT_(QS_QF_MPOOL_GET,
QS_priv_.locFilter[MP_OBJ], me->start)
QS_TIME_(); /* timestamp */
QS_OBJ_(me->start); /* the memory managed by this pool */
QS_MPC_(me->nFree); /* # of free blocks in the pool */
QS_MPC_(me->nMin); /* min # free blocks ever in the pool */
QS_END_NOCRIT_()
}
/* don't have enough free blocks at this point */
else {
fb = (QFreeBlock *)0;
QS_BEGIN_NOCRIT_(QS_QF_MPOOL_GET_ATTEMPT,
QS_priv_.locFilter[MP_OBJ], me->start)
QS_TIME_(); /* timestamp */
QS_OBJ_(me->start); /* the memory managed by this pool */
QS_MPC_(me->nFree); /* the number of free blocks in the pool */
QS_MPC_(margin); /* the requested margin */
QS_END_NOCRIT_()
}
QF_CRIT_EXIT_();
return fb; /* return the pointer to memory block or NULL to the caller */
}
/****************************************************************************/
/**
* @description
* This function obtains the minimum number of free blocks in the given
* event pool since this pool has been initialized by a call to QF_poolInit().
*
* @param[in] poolId event pool ID in the range 1..QF_maxPool_, where
* QF_maxPool_ is the number of event pools initialized
* with the function QF_poolInit().
*
* @returns the minimum number of unused blocks in the given event pool.
*/
uint_fast16_t QF_getPoolMin(uint_fast8_t const poolId) {
uint_fast16_t min;
QF_CRIT_STAT_
/** @pre the poolId must be in range */
Q_REQUIRE_ID(400, ((uint_fast8_t)1 <= poolId)
&& (poolId <= QF_maxPool_));
QF_CRIT_ENTRY_();
min = (uint_fast16_t)QF_pool_[poolId - (uint_fast8_t)1].nMin;
QF_CRIT_EXIT_();
return min;
}