/*
ChibiOS - Copyright (C) 2006..2019 Giovanni Di Sirio.
This file is part of ChibiOS.
ChibiOS is free 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.
ChibiOS 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 .
*/
/**
* @file oslib/src/chobjcaches.c
* @brief Objects Caches code.
* @details Objects caches.
*
Operation mode
* An object cache allows to retrieve and release objects from a
* slow media, for example a disk or flash.
* The most recently used objects are kept in a series of RAM
* buffers making access faster. Objects are identified by a
* pair which could be mapped, for example, to a
* disk drive identifier and sector identifier.
* Read and write operations are performed using externally-supplied
* functions, the cache is device-agnostic.
* The cache uses internally an hash table, the size of the table
* should be dimensioned to minimize the risk of hash collisions,
* a factor of two is usually acceptable, it depends on the specific
* application requirements.
* Operations defined for caches:
* - Get Object: Retrieves an object from cache, if not
* present then an empty buffer is returned.
* - Read Object: Retrieves an object from cache, if not
* present a buffer is allocated and the object is read from the
* media.
* - Release Object: Releases an object to the cache handling
* the media update, if required.
* .
* @pre In order to use the pipes APIs the @p CH_CFG_USE_OBJ_CACHES
* option must be enabled in @p chconf.h.
* @note Compatible with RT and NIL.
*
* @addtogroup oslib_objchaches
* @{
*/
#include "ch.h"
#if (CH_CFG_USE_OBJ_CACHES == TRUE) || defined(__DOXYGEN__)
/*===========================================================================*/
/* Module local definitions. */
/*===========================================================================*/
/* Default hash function.*/
#if !defined(OC_HASH_FUNCTION) || defined(__DOXYGEN__)
#define OC_HASH_FUNCTION(ocp, group, key) \
(((unsigned)(group) + (unsigned)(key)) & ((unsigned)(ocp)->hashn - 1U))
#endif
/* Insertion into an hash slot list.*/
#define HASH_INSERT(ocp, objp, group, key) { \
oc_hash_header_t *hhp; \
(hhp) = &(ocp)->hashp[OC_HASH_FUNCTION(ocp, group, key)]; \
(objp)->hash_next = (hhp)->hash_next; \
(objp)->hash_prev = (oc_object_t *)(hhp); \
(hhp)->hash_next->hash_prev = (objp); \
(hhp)->hash_next = (objp); \
}
/* Removal of an object from the hash.*/
#define HASH_REMOVE(objp) { \
(objp)->hash_prev->hash_next = (objp)->hash_next; \
(objp)->hash_next->hash_prev = (objp)->hash_prev; \
}
/* Insertion on LRU list head (newer objects).*/
#define LRU_INSERT_HEAD(ocp, objp) { \
(objp)->lru_next = (ocp)->lru.lru_next; \
(objp)->lru_prev = (oc_object_t *)&(ocp)->lru; \
(ocp)->lru.lru_next->lru_prev = (objp); \
(ocp)->lru.lru_next = (objp); \
}
/* Insertion on LRU list head (newer objects).*/
#define LRU_INSERT_TAIL(ocp, objp) { \
(objp)->lru_prev = (ocp)->lru.lru_prev; \
(objp)->lru_next = (oc_object_t *)&(ocp)->lru; \
(ocp)->lru.lru_prev->lru_next = (objp); \
(ocp)->lru.lru_prev = (objp); \
}
/* Removal of an object from the LRU list.*/
#define LRU_REMOVE(objp) { \
(objp)->lru_prev->lru_next = (objp)->lru_next; \
(objp)->lru_next->lru_prev = (objp)->lru_prev; \
}
/*===========================================================================*/
/* Module exported variables. */
/*===========================================================================*/
/*===========================================================================*/
/* Module local types. */
/*===========================================================================*/
/*===========================================================================*/
/* Module local variables. */
/*===========================================================================*/
/*===========================================================================*/
/* Module local functions. */
/*===========================================================================*/
/**
* @brief Returns an object pointer from the cache, if present.
*
* @param[out] ocp pointer to the @p objects_cache_t structure to be
* @param[in] group object group identifier
* @param[in] key object identifier within the group
* initialized
* @return The pointer to the retrieved object.
* @retval NULL if the object is not in cache.
*
* @notapi
*/
static oc_object_t *hash_get_s(objects_cache_t *ocp,
uint32_t group,
uint32_t key) {
oc_hash_header_t *hhp;
oc_object_t *objp;
/* Hash slot where to search for an hit.*/
hhp = &ocp->hashp[OC_HASH_FUNCTION(ocp, group, key)];
objp = hhp->hash_next;
/* Scanning the siblings collision list.*/
while (objp != (oc_object_t *)hhp) {
if ((objp->obj_key == key) && (objp->obj_group == group)) {
/* Cache hit.*/
return objp;
}
objp = objp->hash_next;
}
return NULL;
}
/**
* @brief Gets the least recently used object buffer from the LRU list.
*
* @param[out] ocp pointer to the @p objects_cache_t structure to be
* @return The pointer to the retrieved object.
*
* @notapi
*/
static oc_object_t *lru_get_last_s(objects_cache_t *ocp) {
oc_object_t *objp;
while (true) {
/* Waiting for an object buffer to become available in the LRU.*/
(void) chSemWaitS(&ocp->lru_sem);
/* Now an object buffer is in the LRU for sure, taking it from the
LRU tail.*/
objp = ocp->lru.lru_prev;
chDbgAssert((objp->obj_flags & OC_FLAG_INLRU) == OC_FLAG_INLRU,
"not in LRU");
chDbgAssert(chSemGetCounterI(&objp->obj_sem) == (cnt_t)1,
"semaphore counter not 1");
LRU_REMOVE(objp);
objp->obj_flags &= ~OC_FLAG_INLRU;
/* Getting the object semaphore, we know there is no wait so
using the "fast" variant.*/
chSemFastWaitI(&objp->obj_sem);
/* If it is a buffer not needing (lazy) write then it can be used
right away.*/
if ((objp->obj_flags & OC_FLAG_LAZYWRITE) == 0U) {
/* Removing from hash table if required.*/
if ((objp->obj_flags & OC_FLAG_INHASH) != 0U) {
HASH_REMOVE(objp);
}
/* Removing all flags, it is "new" now.*/
objp->obj_flags = 0U;
return objp;
}
/* Out of critical section.*/
chSysUnlock();
/* Invoking the writer asynchronously, it will release the buffer once it
is written. It is responsibility of the write function to release
the buffer.*/
objp->obj_flags = OC_FLAG_INHASH | OC_FLAG_FORGET;
(void) ocp->writef(ocp, objp, true);
/* Critical section enter again.*/
chSysLock();
}
}
/*===========================================================================*/
/* Module exported functions. */
/*===========================================================================*/
/**
* @brief Initializes a @p objects_cache_t object.
*
* @param[out] ocp pointer to the @p objects_cache_t structure to be
* initialized
* @param[in] hashn number of elements in the hash table array, must be
* a power of two and not lower than @p objn
* @param[in] hashp pointer to the hash table as an array of
* @p oc_hash_header_t
* @param[in] objn number of elements in the objects table array
* @param[in] objsz size of elements in the objects table array, the
* minimum value is sizeof (oc_object_t).
* @param[in] objvp pointer to the hash objects as an array of structures
* starting with an @p oc_object_t
* @param[in] readf pointer to an object reader function
* @param[in] writef pointer to an object writer function
*
* @init
*/
void chCacheObjectInit(objects_cache_t *ocp,
ucnt_t hashn,
oc_hash_header_t *hashp,
ucnt_t objn,
size_t objsz,
void *objvp,
oc_readf_t readf,
oc_writef_t writef) {
chDbgCheck((ocp != NULL) && (hashp != NULL) && (objvp != NULL) &&
((hashn & (hashn - (ucnt_t)1)) == (ucnt_t)0) &&
(objn > (ucnt_t)0) && (hashn >= objn) &&
(objsz >= sizeof (oc_object_t)) &&
((objsz & (PORT_NATURAL_ALIGN - 1U)) == 0U));
chSemObjectInit(&ocp->cache_sem, (cnt_t)1);
chSemObjectInit(&ocp->lru_sem, (cnt_t)objn);
ocp->hashn = hashn;
ocp->hashp = hashp;
ocp->objn = objn;
ocp->objvp = objvp;
ocp->readf = readf;
ocp->writef = writef;
ocp->lru.hash_next = NULL;
ocp->lru.hash_prev = NULL;
ocp->lru.lru_next = (oc_object_t *)&ocp->lru;
ocp->lru.lru_prev = (oc_object_t *)&ocp->lru;
/* Hash headers initialization.*/
do {
hashp->hash_next = (oc_object_t *)hashp;
hashp->hash_prev = (oc_object_t *)hashp;
hashp++;
} while (hashp < &ocp->hashp[ocp->hashn]);
/* Object headers initialization.*/
do {
oc_object_t *objp = (oc_object_t *)objvp;
chSemObjectInit(&objp->obj_sem, (cnt_t)1);
LRU_INSERT_HEAD(ocp, objp);
objp->obj_group = 0U;
objp->obj_key = 0U;
objp->obj_flags = OC_FLAG_INLRU;
objp->dptr = NULL;
objvp = (void *)((uint8_t *)objvp + objsz);
objn--;
} while (objn > (ucnt_t)0);
}
/**
* @brief Retrieves an object from the cache.
* @note If the object is not in cache then the returned object is marked
* as @p OC_FLAG_NOTSYNC meaning that its data contains garbage and
* must be initialized.
*
* @param[in] ocp pointer to the @p objects_cache_t structure
* @param[in] group object group identifier
* @param[in] key object identifier within the group
* @return The pointer to the retrieved object.
*
* @api
*/
oc_object_t *chCacheGetObject(objects_cache_t *ocp,
uint32_t group,
uint32_t key) {
oc_object_t *objp;
/* Critical section enter, the hash check operation is fast.*/
chSysLock();
/* Checking the cache for a hit.*/
objp = hash_get_s(ocp, group, key);
if (objp != NULL) {
chDbgAssert((objp->obj_flags & OC_FLAG_INHASH) == OC_FLAG_INHASH,
"not in hash");
/* Cache hit, checking if the buffer is owned by some
other thread.*/
if (chSemGetCounterI(&objp->obj_sem) > (cnt_t)0) {
/* Not owned case, it is in the LRU list.*/
chDbgAssert((objp->obj_flags & OC_FLAG_INLRU) == OC_FLAG_INLRU,
"not in LRU");
/* Removing the object from LRU, now it is "owned".*/
LRU_REMOVE(objp);
objp->obj_flags &= ~OC_FLAG_INLRU;
/* Getting the object semaphore, we know there is no wait so
using the "fast" variant.*/
chSemFastWaitI(&objp->obj_sem);
}
else {
/* Owned case, some other thread is playing with this object, we
need to wait.*/
chDbgAssert((objp->obj_flags & OC_FLAG_INLRU) == 0U, "in LRU");
/* Waiting on the buffer semaphore.*/
(void) chSemWaitS(&objp->obj_sem);
}
}
else {
/* Cache miss, getting an object buffer from the LRU list.*/
objp = lru_get_last_s(ocp);
/* Naming this object and publishing it in the hash table.*/
objp->obj_group = group;
objp->obj_key = key;
objp->obj_flags = OC_FLAG_INHASH | OC_FLAG_NOTSYNC;
HASH_INSERT(ocp, objp, group, key);
}
/* Out of critical section and returning the object.*/
chSysUnlock();
return objp;
}
/**
* @brief Releases an object into the cache.
* @note This function gives a meaning to the following flags:
* - @p OC_FLAG_INLRU must be cleared.
* - @p OC_FLAG_INHASH must be set.
* - @p OC_FLAG_SHARED must be cleared.
* - @p OC_FLAG_NOTSYNC invalidates the object and queues it on
* the LRU tail.
* - @p OC_FLAG_LAZYWRITE is ignored and kept, a write will occur
* when the object is removed from the LRU list (lazy write).
* .
*
* @param[in] ocp pointer to the @p objects_cache_t structure
* @param[in] objp pointer to the @p oc_object_t structure
*
* @iclass
*/
void chCacheReleaseObjectI(objects_cache_t *ocp,
oc_object_t *objp) {
/* Checking initial conditions of the object to be released.*/
chDbgAssert((objp->obj_flags & (OC_FLAG_INLRU |
OC_FLAG_INHASH |
OC_FLAG_SHARED)) == OC_FLAG_INHASH,
"invalid object state");
chDbgAssert(chSemGetCounterI(&objp->obj_sem) <= (cnt_t)0,
"semaphore counter greater than 0");
/* If some thread is waiting for this specific buffer then it is
handed directly without going through the LRU.*/
if (chSemGetCounterI(&objp->obj_sem) < (cnt_t)0) {
/* Clearing all flags except those that are still meaningful, note,
OC_FLAG_NOTSYNC and OC_FLAG_LAZYWRITE are passed, the other thread
will handle them.*/
objp->obj_flags &= OC_FLAG_INHASH | OC_FLAG_NOTSYNC | OC_FLAG_LAZYWRITE;
chSemSignalI(&objp->obj_sem);
return;
}
/* If the object specifies OC_FLAG_NOTSYNC then it must be invalidated
and removed from the hash table.*/
if ((objp->obj_flags & OC_FLAG_NOTSYNC) != 0U) {
HASH_REMOVE(objp);
LRU_INSERT_TAIL(ocp, objp);
objp->obj_group = 0U;
objp->obj_key = 0U;
objp->obj_flags = OC_FLAG_INLRU;
}
else {
/* LRU insertion point depends on the OC_FLAG_FORGET flag.*/
if ((objp->obj_flags & OC_FLAG_FORGET) == 0U) {
/* Placing it on head.*/
LRU_INSERT_HEAD(ocp, objp);
}
else {
/* Low priority data, placing it on tail.*/
LRU_INSERT_TAIL(ocp, objp);
}
objp->obj_flags &= OC_FLAG_INHASH | OC_FLAG_LAZYWRITE;
objp->obj_flags |= OC_FLAG_INLRU;
}
/* Increasing the LRU counter semaphore.*/
chSemSignalI(&ocp->lru_sem);
/* Releasing the object, we know there are no threads waiting so
using the "fast" signal variant.*/
chSemFastSignalI(&objp->obj_sem);
}
/**
* @brief Reads object data from the storage.
* @note In case of asynchronous operation an error condition is not
* reported by this function.
*
* @param[in] ocp pointer to the @p objects_cache_t structure
* @param[in] objp pointer to the @p oc_object_t structure
* @param[in] async requests an asynchronous operation if supported, the
* function is then responsible for releasing the
* object
* @return The operation status. In case of asynchronous
* operation @p false is always returned.
* @retval false if the operation succeeded.
* @retval true if the synchronous read operation failed.
*
* @api
*/
bool chCacheReadObject(objects_cache_t *ocp,
oc_object_t *objp,
bool async) {
/* Marking it as OC_FLAG_NOTSYNC because the read operation is going
to corrupt it in case of failure. It is responsibility of the read
implementation to clear it if the operation succeeds.*/
objp->obj_flags |= OC_FLAG_NOTSYNC;
return ocp->readf(ocp, objp, async);
}
/**
* @brief Writes the object data back to storage.
* @note In case of asynchronous operation an error condition is not
* reported by this function.
*
* @param[in] ocp pointer to the @p objects_cache_t structure
* @param[in] objp pointer to the @p oc_object_t structure
* @param[in] async requests an asynchronous operation if supported, the
* function is then responsible for releasing the
* object
* @return The operation status. In case of asynchronous
* operation @p false is always returned.
* @retval false if the operation succeeded.
* @retval true if the synchronous write operation failed.
*
* @api
*/
bool chCacheWriteObject(objects_cache_t *ocp,
oc_object_t *objp,
bool async) {
/* Resetting the OC_FLAG_LAZYWRITE flag in order to prevent multiple
writes.*/
objp->obj_flags &= ~OC_FLAG_LAZYWRITE;
return ocp->writef(ocp, objp, async);
}
#endif /* CH_CFG_USE_OBJ_CACHES == TRUE */
/** @} */