blob: f7d61555ad824c2433ce2f5551ab84bbb44c442e [file] [log] [blame] [edit]
/* GPLv2 or OpenIB.org BSD (MIT) See COPYING file */
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <ccan/list.h>
#include <util/interval_set.h>
#include <util/util.h>
struct iset {
struct list_head head;
pthread_mutex_t lock;
};
struct iset_range {
struct list_node entry;
uint64_t start;
uint64_t length;
};
struct iset *iset_create(void)
{
struct iset *iset;
iset = calloc(1, sizeof(*iset));
if (!iset) {
errno = ENOMEM;
return NULL;
}
pthread_mutex_init(&iset->lock, NULL);
list_head_init(&iset->head);
return iset;
}
void iset_destroy(struct iset *iset)
{
struct iset_range *range, *tmp;
list_for_each_safe(&iset->head, range, tmp, entry)
free(range);
free(iset);
}
static int
range_overlap(uint64_t s1, uint64_t len1, uint64_t s2, uint64_t len2)
{
if (((s1 < s2) && (s1 + len1 - 1 < s2)) ||
((s1 > s2) && (s1 > s2 + len2 - 1)))
return 0;
return 1;
}
static struct iset_range *create_range(uint64_t start, uint64_t length)
{
struct iset_range *range;
range = calloc(1, sizeof(*range));
if (!range) {
errno = ENOMEM;
return NULL;
}
range->start = start;
range->length = length;
return range;
}
static void delete_range(struct iset_range *r)
{
list_del(&r->entry);
free(r);
}
static bool check_do_combine(struct iset *iset,
struct iset_range *p, struct iset_range *n,
uint64_t start, uint64_t length)
{
bool combined2prev = false, combined2next = false;
if (p && (p->start + p->length == start)) {
p->length += length;
combined2prev = true;
}
if (n && (start + length == n->start)) {
if (combined2prev) {
p->length += n->length;
delete_range(n);
} else {
n->start = start;
n->length += length;
}
combined2next = true;
}
return combined2prev || combined2next;
}
int iset_insert_range(struct iset *iset, uint64_t start, uint64_t length)
{
struct iset_range *prev = NULL, *r, *rnew;
bool found = false, combined;
int ret = 0;
if (!length || (start + length - 1 < start)) {
errno = EINVAL;
return errno;
}
pthread_mutex_lock(&iset->lock);
list_for_each(&iset->head, r, entry) {
if (range_overlap(r->start, r->length, start, length)) {
errno = EINVAL;
ret = errno;
goto out;
}
if (r->start > start) {
found = true;
break;
}
prev = r;
}
combined = check_do_combine(iset, prev, found ? r : NULL,
start, length);
if (!combined) {
rnew = create_range(start, length);
if (!rnew) {
ret = errno;
goto out;
}
if (!found)
list_add_tail(&iset->head, &rnew->entry);
else
list_add_before(&iset->head, &r->entry, &rnew->entry);
}
out:
pthread_mutex_unlock(&iset->lock);
return ret;
}
static int power_of_two(uint64_t x)
{
return ((x != 0) && !(x & (x - 1)));
}
int iset_alloc_range(struct iset *iset, uint64_t length,
uint64_t *start, uint64_t alignment)
{
struct iset_range *r, *rnew;
uint64_t astart, rend;
bool found = false;
int ret = 0;
if (!power_of_two(alignment)) {
errno = EINVAL;
return errno;
}
pthread_mutex_lock(&iset->lock);
list_for_each(&iset->head, r, entry) {
astart = align(r->start, alignment);
/* Check for wrap around */
if ((astart + length - 1 >= astart) &&
(astart + length - 1 <= r->start + r->length - 1)) {
found = true;
break;
}
}
if (!found) {
errno = ENOSPC;
ret = errno;
goto out;
}
if (r->start == astart) {
if (r->length == length) { /* Case #1 */
delete_range(r);
} else { /* Case #2 */
r->start += length;
r->length -= length;
}
} else {
rend = r->start + r->length;
if (astart + length != rend) { /* Case #4 */
rnew = create_range(astart + length,
rend - astart - length);
if (!rnew) {
ret = errno;
goto out;
}
list_add_after(&iset->head, &r->entry, &rnew->entry);
}
r->length = astart - r->start; /* Case #3 & #4 */
}
*start = astart;
out:
pthread_mutex_unlock(&iset->lock);
return ret;
}