| /* 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; |
| } |