blob: 80e039ef8e5ebecf3be3cac7658927cc9334bfa9 [file] [log] [blame]
/*
* Copyright 2014, NICTA
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(NICTA_BSD)
*/
#include <autoconf.h>
#include <allocman/utspace/trickle.h>
#include <allocman/allocman.h>
#include <allocman/util.h>
#include <sel4/sel4.h>
#include <vka/object.h>
#ifdef CONFIG_KERNEL_STABLE
static inline size_t _make_bitmap(size_t bits) {
/* avoid shift by int max in BIT/MASK code */
if (BIT(bits - 1) == CONFIG_WORD_SIZE) {
return -1;
} else {
/* shift the result up so that the high bits are set */
return MASK(BIT(bits - 1)) << (CONFIG_WORD_SIZE - BIT(bits - 1));
}
}
static inline void _insert_node(struct utspace_trickle_node **head, struct utspace_trickle_node *node)
{
if (*head) {
(*head)->prev = node;
}
node->next = *head;
node->prev = NULL;
*head = node;
}
static inline void _remove_node(struct utspace_trickle_node **head, struct utspace_trickle_node *node)
{
if (node->next) {
node->next->prev = node->prev;
}
if (node->prev) {
node->prev->next = node->next;
} else {
*head = node->next;
}
}
static inline seL4_Word _make_cookie(struct utspace_trickle_node *node, size_t offset)
{
size_t int_node = (size_t) node;
assert( (int_node % (CONFIG_WORD_SIZE)) == 0);
assert(offset < CONFIG_WORD_SIZE);
return int_node | offset;
}
static inline struct utspace_trickle_node *_cookie_to_node(seL4_Word cookie) {
cookie -= cookie % (CONFIG_WORD_SIZE);
return (struct utspace_trickle_node*)cookie;
}
static inline size_t _cookie_to_offset(seL4_Word cookie)
{
return cookie % (CONFIG_WORD_SIZE);
}
static inline struct utspace_trickle_node *_make_node(struct allocman *alloc, int *error) {
uintptr_t addr = (uintptr_t)allocman_mspace_alloc(alloc, sizeof(struct utspace_trickle_node) * 2 - sizeof(size_t), error);
struct utspace_trickle_node *node;
if (*error) {
return NULL;
}
node = _cookie_to_node(addr + CONFIG_WORD_SIZE);
node->padding = addr;
return node;
}
static inline void _free_node(struct allocman *alloc, struct utspace_trickle_node *node)
{
allocman_mspace_free(alloc, (void*)node->padding, sizeof(struct utspace_trickle_node) * 2 - sizeof(size_t));
}
int _utspace_trickle_add_uts(allocman_t *alloc, void *_trickle, size_t num, const cspacepath_t *uts, size_t *size_bits, uintptr_t *paddr) {
utspace_trickle_t *trickle = (utspace_trickle_t*) _trickle;
struct utspace_trickle_node *nodes[num];
cspacepath_t *uts_copy[num];
int error;
size_t i;
for (i = 0; i < num; i++) {
nodes[i] = _make_node(alloc, &error);
if (error) {
for (i--; i >= 0; i--) {
_free_node(alloc, nodes[i]);
allocman_mspace_free(alloc, uts_copy[i], sizeof(cspacepath_t));
}
return error;
}
uts_copy[i] = allocman_mspace_alloc(alloc, sizeof(cspacepath_t), &error);
if (error) {
_free_node(alloc, nodes[i]);
for (i--; i >= 0; i--) {
_free_node(alloc, nodes[i]);
allocman_mspace_free(alloc, uts_copy[i], sizeof(cspacepath_t));
}
}
}
for (i = 0; i < num; i++) {
*uts_copy[i] = uts[i];
nodes[i]->ut = uts_copy[i];
nodes[i]->offset = 0;
nodes[i]->paddr = paddr[i];
nodes[i]->parent_cookie = 0;
nodes[i]->next = nodes[i]->prev = NULL;
/* Start with only 1 thing free */
nodes[i]->bitmap = BIT(31);
nodes[i]->bitmap_bits = 1;
_insert_node(&trickle->heads[size_bits[i]], nodes[i]);
}
return 0;
}
void utspace_trickle_create(utspace_trickle_t *trickle)
{
uint32_t i;
for (i = 0; i < CONFIG_WORD_SIZE; i++) {
trickle->heads[i] = NULL;
}
}
static int _refill_pool(struct allocman *alloc, utspace_trickle_t *trickle, size_t size_bits)
{
size_t i;
int error;
struct utspace_trickle_node *node;
seL4_Word cookie;
node = _make_node(alloc, &error);
if (error) {
return error;
}
/* Check if there are untypeds >= 5 size_bits from us */
for (i = size_bits + 5 > CONFIG_WORD_SIZE - 1 ? CONFIG_WORD_SIZE - 1 : size_bits + 5; i < CONFIG_WORD_SIZE; i++) {
if (trickle->heads[i]) {
i = size_bits + 5;
break;
}
}
if (i == CONFIG_WORD_SIZE) {
/* Search for the biggest one near us */
for (i = size_bits + 5 > CONFIG_WORD_SIZE - 1 ? CONFIG_WORD_SIZE - 1 : size_bits + 5; i > size_bits; i--) {
if (trickle->heads[i]) {
break;
}
}
}
if (i != size_bits) {
cookie = _utspace_trickle_alloc(alloc, trickle, i, seL4_UntypedObject, NULL, &error);
if (!error) {
struct utspace_trickle_node *parent = _cookie_to_node(cookie);
size_t offset = _cookie_to_offset(cookie);
node->ut = parent->ut;
node->offset = parent->offset + (offset << (i));
if (parent->paddr) {
node->paddr = parent->paddr + (offset << (i));
} else {
node->paddr = 0;
}
node->parent_cookie = cookie;
node->bitmap_bits = i - size_bits + 1;
node->bitmap = _make_bitmap(node->bitmap_bits);
node->next = node->prev = NULL;
_insert_node(&trickle->heads[size_bits], node);
return 0;
}
}
_free_node(alloc, node);
return 1;
}
seL4_Word _utspace_trickle_alloc(struct allocman *alloc, void *_trickle, size_t size_bits, seL4_Word type, const cspacepath_t *slot, int *error)
{
size_t sel4_size_bits;
int _error;
utspace_trickle_t *trickle = (utspace_trickle_t*)_trickle;
struct utspace_trickle_node *node;
size_t offset;
size_t mem_offset;
/* get size of untyped call */
sel4_size_bits = get_sel4_object_size(type, size_bits);
if (size_bits != vka_get_object_size(type, sel4_size_bits) || size_bits == 0) {
SET_ERROR(error, 1);
return 0;
}
assert(size_bits < CONFIG_WORD_SIZE);
if (!trickle->heads[size_bits]) {
_error = _refill_pool(alloc, trickle, size_bits);
if (_error) {
SET_ERROR(error, _error);
return 0;
}
}
node = trickle->heads[size_bits];
offset = CLZL(node->bitmap);
mem_offset = node->offset + (offset << size_bits);
if (slot) {
_error = seL4_Untyped_RetypeAtOffset(node->ut->capPtr, type, mem_offset, sel4_size_bits, slot->root, slot->dest, slot->destDepth, slot->offset, 1);
if (_error != seL4_NoError) {
SET_ERROR(error, 1);
return 0;
}
}
node->bitmap &= ~BIT(CONFIG_WORD_SIZE - 1 - offset);
if (node->bitmap == 0) {
_remove_node(&trickle->heads[size_bits], node);
}
SET_ERROR(error, 0);
return _make_cookie(node, offset);
}
void _utspace_trickle_free(struct allocman *alloc, void *_trickle, seL4_Word cookie, size_t size_bits)
{
utspace_trickle_t *trickle = (utspace_trickle_t*)_trickle;
struct utspace_trickle_node *node = _cookie_to_node(cookie);
size_t offset = _cookie_to_offset(cookie);
int in_list = !(node->bitmap == 0);
node->bitmap |= BIT(CONFIG_WORD_SIZE - 1 - offset);
if (node->bitmap == _make_bitmap(node->bitmap_bits)) {
if (node->parent_cookie) {
if (in_list) {
_remove_node(&trickle->heads[size_bits], node);
}
_utspace_trickle_free(alloc, trickle, node->parent_cookie, size_bits + node->bitmap_bits - 1);
_free_node(alloc, node);
} else if (!in_list) {
_insert_node(&trickle->heads[size_bits], node);
}
} else if (!in_list) {
_insert_node(&trickle->heads[size_bits], node);
}
}
uintptr_t _utspace_trickle_paddr(void *_trickle, seL4_Word cookie, size_t size_bits) {
struct utspace_trickle_node *node = _cookie_to_node(cookie);
size_t offset = _cookie_to_offset(cookie);
return node->paddr + (offset << size_bits);
}
#endif