#include <machine/rtems-bsd-kernel-space.h>
#include <rtems/bsd/local/opt_dpaa.h>
/* Copyright 2009 - 2015 Freescale Semiconductor, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Freescale Semiconductor nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* ALTERNATIVELY, this software may be distributed under the terms of the
* GNU General Public License ("GPL") as published by the Free Software
* Foundation, either version 2 of that License or (at your option) any
* later version.
*
* THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Freescale Semiconductor BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if defined(CONFIG_FSL_BMAN_PORTAL) || \
defined(CONFIG_FSL_BMAN_PORTAL_MODULE) || \
defined(CONFIG_FSL_QMAN_PORTAL) || \
defined(CONFIG_FSL_QMAN_PORTAL_MODULE)
#include "dpaa_sys.h"
/* The allocator is a (possibly-empty) list of these */
struct dpaa_resource_node {
struct list_head list;
u32 base;
u32 num;
/* refcount and is_alloced are only set
when the node is in the used list */
unsigned int refcount;
int is_alloced;
};
#ifdef DPAA_RESOURCE_DEBUG
#define DPRINT pr_info
static void DUMP(struct dpaa_resource *alloc)
{
int off = 0;
char buf[256];
struct dpaa_resource_node *p;
pr_info("Free Nodes\n");
list_for_each_entry(p, &alloc->free, list) {
if (off < 255)
off += snprintf(buf + off, 255-off, "{%d,%d}",
p->base, p->base + p->num - 1);
}
pr_info("%s\n", buf);
off = 0;
pr_info("Used Nodes\n");
list_for_each_entry(p, &alloc->used, list) {
if (off < 255)
off += snprintf(buf + off, 255-off, "{%d,%d}",
p->base, p->base + p->num - 1);
}
pr_info("%s\n", buf);
}
#else
#define DPRINT(x...)
#define DUMP(a)
#endif
int dpaa_resource_new(struct dpaa_resource *alloc, u32 *result,
u32 count, u32 align, int partial)
{
struct dpaa_resource_node *i = NULL, *next_best = NULL,
*used_node = NULL;
u32 base, next_best_base = 0, num = 0, next_best_num = 0;
struct dpaa_resource_node *margin_left, *margin_right;
*result = (u32)-1;
DPRINT("alloc_range(%d,%d,%d)\n", count, align, partial);
DUMP(alloc);
/* If 'align' is 0, it should behave as though it was 1 */
if (!align)
align = 1;
margin_left = kmalloc(sizeof(*margin_left), GFP_KERNEL);
if (!margin_left)
goto err;
margin_right = kmalloc(sizeof(*margin_right), GFP_KERNEL);
if (!margin_right) {
kfree(margin_left);
goto err;
}
spin_lock_irq(&alloc->lock);
list_for_each_entry(i, &alloc->free, list) {
base = (i->base + align - 1) / align;
base *= align;
if ((base - i->base) >= i->num)
/* alignment is impossible, regardless of count */
continue;
num = i->num - (base - i->base);
if (num >= count) {
/* this one will do nicely */
num = count;
goto done;
}
if (num > next_best_num) {
next_best = i;
next_best_base = base;
next_best_num = num;
}
}
if (partial && next_best) {
i = next_best;
base = next_best_base;
num = next_best_num;
} else
i = NULL;
done:
if (i) {
if (base != i->base) {
margin_left->base = i->base;
margin_left->num = base - i->base;
list_add_tail(&margin_left->list, &i->list);
} else
kfree(margin_left);
if ((base + num) < (i->base + i->num)) {
margin_right->base = base + num;
margin_right->num = (i->base + i->num) -
(base + num);
list_add(&margin_right->list, &i->list);
} else
kfree(margin_right);
list_del(&i->list);
kfree(i);
*result = base;
}
spin_unlock_irq(&alloc->lock);
err:
DPRINT("returning %d\n", i ? num : -ENOMEM);
DUMP(alloc);
if (!i)
return -ENOMEM;
/* Add the allocation to the used list with a refcount of 1 */
used_node = kmalloc(sizeof(*used_node), GFP_KERNEL);
if (!used_node)
return -ENOMEM;
used_node->base = *result;
used_node->num = num;
used_node->refcount = 1;
used_node->is_alloced = 1;
list_add_tail(&used_node->list, &alloc->used);
return (int)num;
}
EXPORT_SYMBOL(dpaa_resource_new);
/* Allocate the list node using GFP_ATOMIC, because we *really* want to avoid
* forcing error-handling on to users in the deallocation path. */
static void _dpaa_resource_free(struct dpaa_resource *alloc, u32 base_id,
u32 count)
{
struct dpaa_resource_node *i,
*node = kmalloc(sizeof(*node), GFP_ATOMIC);
BUG_ON(!node);
DPRINT("release_range(%d,%d)\n", base_id, count);
DUMP(alloc);
BUG_ON(!count);
spin_lock_irq(&alloc->lock);
node->base = base_id;
node->num = count;
list_for_each_entry(i, &alloc->free, list) {
if (i->base >= node->base) {
/* BUG_ON(any overlapping) */
BUG_ON(i->base < (node->base + node->num));
list_add_tail(&node->list, &i->list);
goto done;
}
}
list_add_tail(&node->list, &alloc->free);
done:
/* Merge to the left */
i = list_entry(node->list.prev, struct dpaa_resource_node, list);
if (node->list.prev != &alloc->free) {
BUG_ON((i->base + i->num) > node->base);
if ((i->base + i->num) == node->base) {
node->base = i->base;
node->num += i->num;
list_del(&i->list);
kfree(i);
}
}
/* Merge to the right */
i = list_entry(node->list.next, struct dpaa_resource_node, list);
if (node->list.next != &alloc->free) {
BUG_ON((node->base + node->num) > i->base);
if ((node->base + node->num) == i->base) {
node->num += i->num;
list_del(&i->list);
kfree(i);
}
}
spin_unlock_irq(&alloc->lock);
DUMP(alloc);
}
static void dpaa_resource_free(struct dpaa_resource *alloc, u32 base_id,
u32 count)
{
struct dpaa_resource_node *i = NULL;
spin_lock_irq(&alloc->lock);
/* First find the node in the used list and decrement its ref count */
list_for_each_entry(i, &alloc->used, list) {
if (i->base == base_id && i->num == count) {
--i->refcount;
if (i->refcount == 0) {
list_del(&i->list);
spin_unlock_irq(&alloc->lock);
if (i->is_alloced)
_dpaa_resource_free(alloc, base_id,
count);
kfree(i);
return;
}
spin_unlock_irq(&alloc->lock);
return;
}
}
/* Couldn't find the allocation */
pr_err("Attempt to free ID 0x%x COUNT %d that wasn't alloc'd or reserved\n",
base_id, count);
spin_unlock_irq(&alloc->lock);
}
/* Same as free but no previous allocation checking is needed */
void dpaa_resource_seed(struct dpaa_resource *alloc, u32 base_id, u32 count)
{
_dpaa_resource_free(alloc, base_id, count);
}
EXPORT_SYMBOL(dpaa_resource_seed);
/* Like 'new' but specifies the desired range, returns -ENOMEM if the entire
* desired range is not available, or 0 for success
*/
int dpaa_resource_reserve(struct dpaa_resource *alloc, u32 base, u32 num)
{
struct dpaa_resource_node *i = NULL, *used_node;
DPRINT("alloc_reserve(%d,%d)\n", base, num);
DUMP(alloc);
spin_lock_irq(&alloc->lock);
/* Check for the node in the used list.
If found, increase it's refcount */
list_for_each_entry(i, &alloc->used, list) {
if ((i->base == base) && (i->num == num)) {
++i->refcount;
spin_unlock_irq(&alloc->lock);
return 0;
}
if ((base >= i->base) && (base < (i->base + i->num))) {
/* This is an attempt to reserve a region that was
already reserved or alloced with a different
base or num */
pr_err("Cannot reserve %d - %d, it overlaps with"
" existing reservation from %d - %d\n",
base, base + num - 1, i->base,
i->base + i->num - 1);
spin_unlock_irq(&alloc->lock);
return -1;
}
}
/* Check to make sure this ID isn't in the free list */
list_for_each_entry(i, &alloc->free, list) {
if ((base >= i->base) && (base < (i->base + i->num))) {
/* yep, the reservation is within this node */
pr_err("Cannot reserve %d - %d, it overlaps with"
" free range %d - %d and must be alloced\n",
base, base + num - 1,
i->base, i->base + i->num - 1);
spin_unlock_irq(&alloc->lock);
return -1;
}
}
/* Add the allocation to the used list with a refcount of 1 */
used_node = kmalloc(sizeof(*used_node), GFP_KERNEL);
if (!used_node) {
spin_unlock_irq(&alloc->lock);
return -ENOMEM;
}
used_node->base = base;
used_node->num = num;
used_node->refcount = 1;
used_node->is_alloced = 0;
list_add_tail(&used_node->list, &alloc->used);
spin_unlock_irq(&alloc->lock);
return 0;
}
EXPORT_SYMBOL(dpaa_resource_reserve);
/* This is a sort-of-conditional dpaa_resource_free() routine. Eg. when
* releasing FQIDs (probably from user-space), it can filter out those
* that aren't in the OOS state (better to leak a h/w resource than to
* crash). This function returns the number of invalid IDs that were not
* released.
*/
u32 dpaa_resource_release(struct dpaa_resource *alloc,
u32 id, u32 count, int (*is_valid)(u32 id))
{
int valid_mode = 0;
u32 loop = id, total_invalid = 0;
while (loop < (id + count)) {
int isvalid = is_valid ? is_valid(loop) : 1;
if (!valid_mode) {
/* We're looking for a valid ID to terminate an invalid
* range */
if (isvalid) {
/* We finished a range of invalid IDs, a valid
* range is now underway */
valid_mode = 1;
count -= (loop - id);
id = loop;
} else
total_invalid++;
} else {
/* We're looking for an invalid ID to terminate a
* valid range */
if (!isvalid) {
/* Release the range of valid IDs, an unvalid
* range is now underway */
if (loop > id)
dpaa_resource_free(alloc, id,
loop - id);
valid_mode = 0;
}
}
loop++;
}
/* Release any unterminated range of valid IDs */
if (valid_mode && count)
dpaa_resource_free(alloc, id, count);
return total_invalid;
}
EXPORT_SYMBOL(dpaa_resource_release);
#endif /* CONFIG_FSL_*MAN_PORTAL* */