/*
* Copyright (c) 2020 Red Hat, Inc.
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* $Id: //eng/vdo-releases/aluminum/src/c++/vdo/kernel/bufferPool.c#1 $
*/
#include "bufferPool.h"
#include <linux/delay.h>
#include <linux/sort.h>
#include "logger.h"
#include "memoryAlloc.h"
#include "statusCodes.h"
/*
* For list nodes on the free-object list, the data field describes
* the object available for reuse.
*
* For nodes on the "spare" list, the data field is meaningless;
* they're just nodes available for use when we need to add an object
* pointer to the freeObjectList.
*
* These are both "free lists", in a sense; don't get confused!
*/
typedef struct {
struct list_head list; // links in current list
void *data; // element data, if on free list
} BufferElement;
struct bufferPool {
const char *name; // Pool name
void *data; // Associated pool data
spinlock_t lock; // Locks this object
unsigned int size; // Total number of buffers
struct list_head freeObjectList; // List of free buffers
struct list_head spareListNodes; // Unused list nodes
unsigned int numBusy; // Number of buffers in use
unsigned int maxBusy; // Maximum value of the above
BufferAllocateFunction *alloc; // Allocate function for buffer data
BufferFreeFunction *free; // Free function for buffer data
BufferDumpFunction *dump; // Dump function for buffer data
BufferElement *bhead; // Array of BufferElement structures
void **objects;
};
/*************************************************************************/
int makeBufferPool(const char *poolName,
unsigned int size,
BufferAllocateFunction *allocateFunction,
BufferFreeFunction *freeFunction,
BufferDumpFunction *dumpFunction,
void *poolData,
BufferPool **poolPtr)
{
BufferPool *pool;
int result = ALLOCATE(1, BufferPool, "buffer pool", &pool);
if (result != VDO_SUCCESS) {
logError("buffer pool allocation failure %d", result);
return result;
}
result = ALLOCATE(size, BufferElement, "buffer pool elements", &pool->bhead);
if (result != VDO_SUCCESS) {
logError("buffer element array allocation failure %d", result);
freeBufferPool(&pool);
return result;
}
result = ALLOCATE(size, void *, "object pointers", &pool->objects);
if (result != VDO_SUCCESS) {
logError("buffer object array allocation failure %d", result);
freeBufferPool(&pool);
return result;
}
pool->name = poolName;
pool->alloc = allocateFunction;
pool->free = freeFunction;
pool->dump = dumpFunction;
pool->data = poolData;
pool->size = size;
spin_lock_init(&pool->lock);
INIT_LIST_HEAD(&pool->freeObjectList);
INIT_LIST_HEAD(&pool->spareListNodes);
BufferElement *bh = pool->bhead;
for (int i = 0; i < pool->size; i++) {
result = pool->alloc(pool->data, &bh->data);
if (result != VDO_SUCCESS) {
logError("verify buffer data allocation failure %d", result);
freeBufferPool(&pool);
return result;
}
pool->objects[i] = bh->data;
list_add(&bh->list, &pool->freeObjectList);
bh++;
}
pool->numBusy = pool->maxBusy = 0;
*poolPtr = pool;
return VDO_SUCCESS;
}
/*************************************************************************/
void freeBufferPool(BufferPool **poolPtr)
{
BufferPool *pool = *poolPtr;
if (pool == NULL) {
return;
}
ASSERT_LOG_ONLY((pool->numBusy == 0), "freeing busy buffer pool, numBusy=%d",
pool->numBusy);
if (pool->objects != NULL) {
for (int i = 0; i < pool->size; i++) {
if (pool->objects[i] != NULL) {
pool->free(pool->data, pool->objects[i]);
}
}
FREE(pool->objects);
}
FREE(pool->bhead);
FREE(pool);
*poolPtr = NULL;
}
/*************************************************************************/
static bool inFreeList(BufferPool *pool, void *data)
{
struct list_head *node;
list_for_each(node, &pool->freeObjectList) {
if (container_of(node, BufferElement, list)->data == data) {
return true;
}
}
return false;
}
/*************************************************************************/
void dumpBufferPool(BufferPool *pool, bool dumpElements)
{
// In order that syslog can empty its buffer, sleep after 35 elements for
// 4ms (till the second clock tick). These numbers chosen in October
// 2012 running on an lfarm.
enum { ELEMENTS_PER_BATCH = 35 };
enum { SLEEP_FOR_SYSLOG = 4 };
if (pool == NULL) {
return;
}
spin_lock(&pool->lock);
logInfo("%s: %u of %u busy (max %u)", pool->name, pool->numBusy, pool->size,
pool->maxBusy);
if (dumpElements && (pool->dump != NULL)) {
int dumped = 0;
for (int i = 0; i < pool->size; i++) {
if (!inFreeList(pool, pool->objects[i])) {
pool->dump(pool->data, pool->objects[i]);
if (++dumped >= ELEMENTS_PER_BATCH) {
spin_unlock(&pool->lock);
dumped = 0;
msleep(SLEEP_FOR_SYSLOG);
spin_lock(&pool->lock);
}
}
}
}
spin_unlock(&pool->lock);
}
/*************************************************************************/
int allocBufferFromPool(BufferPool *pool, void **dataPtr)
{
if (pool == NULL) {
return UDS_INVALID_ARGUMENT;
}
spin_lock(&pool->lock);
if (unlikely(list_empty(&pool->freeObjectList))) {
spin_unlock(&pool->lock);
logDebug("no free buffers");
return -ENOMEM;
}
BufferElement *bh = list_first_entry(&pool->freeObjectList, BufferElement,
list);
list_move(&bh->list, &pool->spareListNodes);
pool->numBusy++;
if (pool->numBusy > pool->maxBusy) {
pool->maxBusy = pool->numBusy;
}
*dataPtr = bh->data;
spin_unlock(&pool->lock);
return VDO_SUCCESS;
}
/*************************************************************************/
static bool freeBufferToPoolInternal(BufferPool *pool, void *data)
{
if (unlikely(list_empty(&pool->spareListNodes))) {
return false;
}
BufferElement *bh = list_first_entry(&pool->spareListNodes, BufferElement,
list);
list_move(&bh->list, &pool->freeObjectList);
bh->data = data;
pool->numBusy--;
return true;
}
/*************************************************************************/
void freeBufferToPool(BufferPool *pool, void *data)
{
spin_lock(&pool->lock);
bool success = freeBufferToPoolInternal(pool, data);
spin_unlock(&pool->lock);
if (!success) {
logDebug("trying to add to free list when already full");
}
}
/*************************************************************************/
void freeBuffersToPool(BufferPool *pool, void **data, int count)
{
spin_lock(&pool->lock);
bool success = true;
for (int i = 0; (i < count) && success; i++) {
success = freeBufferToPoolInternal(pool, data[i]);
}
spin_unlock(&pool->lock);
if (!success) {
logDebug("trying to add to free list when already full");
}
}