#include <sys/param.h>
#include <sys/systm.h>
#include <uvm/uvm.h>
#include <uvm/uvm_addr.h>
#include <sys/pool.h>
#define NUM_PIVOTS 16
#define PIVOT_RND 8
#define PIVOT_EXPIRE 1024
struct pool uaddr_pool;
struct pool uaddr_bestfit_pool;
struct pool uaddr_pivot_pool;
struct pool uaddr_rnd_pool;
struct uaddr_bestfit_state {
struct uvm_addr_state ubf_uaddr;
struct uaddr_free_rbtree ubf_free;
};
struct uaddr_rnd_state {
struct uvm_addr_state ur_uaddr;
#if 0
TAILQ_HEAD(, vm_map_entry) ur_free;
#endif
};
struct uaddr_pivot {
vaddr_t addr;
int expire;
int dir;
struct vm_map_entry *entry;
};
struct uaddr_pivot_state {
struct uvm_addr_state up_uaddr;
struct uaddr_free_rbtree up_free;
struct uaddr_pivot up_pivots[NUM_PIVOTS];
};
extern const struct uvm_addr_functions uaddr_kernel_functions;
struct uvm_addr_state uaddr_kbootstrap;
#ifndef SMALL_KERNEL
struct vm_map_entry *uvm_addr_entrybyspace(struct uaddr_free_rbtree*,
vsize_t);
#endif
void uaddr_destroy(struct uvm_addr_state *);
void uaddr_kbootstrap_destroy(struct uvm_addr_state *);
void uaddr_rnd_destroy(struct uvm_addr_state *);
void uaddr_bestfit_destroy(struct uvm_addr_state *);
void uaddr_pivot_destroy(struct uvm_addr_state *);
#if 0
int uaddr_lin_select(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry **,
vaddr_t *, vsize_t, vaddr_t, vaddr_t, vm_prot_t,
vaddr_t);
#endif
int uaddr_kbootstrap_select(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry **,
vaddr_t *, vsize_t, vaddr_t, vaddr_t, vm_prot_t,
vaddr_t);
int uaddr_rnd_select(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry **,
vaddr_t *, vsize_t, vaddr_t, vaddr_t, vm_prot_t,
vaddr_t);
int uaddr_bestfit_select(struct vm_map *,
struct uvm_addr_state*, struct vm_map_entry **,
vaddr_t *, vsize_t, vaddr_t, vaddr_t, vm_prot_t,
vaddr_t);
#ifndef SMALL_KERNEL
int uaddr_pivot_select(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry **,
vaddr_t *, vsize_t, vaddr_t, vaddr_t, vm_prot_t,
vaddr_t);
int uaddr_stack_brk_select(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry **,
vaddr_t *, vsize_t, vaddr_t, vaddr_t, vm_prot_t,
vaddr_t);
#endif
void uaddr_rnd_insert(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry *);
void uaddr_rnd_remove(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry *);
void uaddr_bestfit_insert(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry *);
void uaddr_bestfit_remove(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry *);
void uaddr_pivot_insert(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry *);
void uaddr_pivot_remove(struct vm_map *,
struct uvm_addr_state *, struct vm_map_entry *);
#ifndef SMALL_KERNEL
vsize_t uaddr_pivot_random(void);
int uaddr_pivot_newpivot(struct vm_map *,
struct uaddr_pivot_state *, struct uaddr_pivot *,
struct vm_map_entry **, vaddr_t *,
vsize_t, vaddr_t, vaddr_t, vsize_t, vsize_t);
#endif
#if defined(DEBUG) || defined(DDB)
void uaddr_pivot_print(struct uvm_addr_state *, boolean_t,
int (*)(const char *, ...));
#if 0
void uaddr_rnd_print(struct uvm_addr_state *, boolean_t,
int (*)(const char *, ...));
#endif
#endif
#ifndef SMALL_KERNEL
struct vm_map_entry *
uvm_addr_entrybyspace(struct uaddr_free_rbtree *free, vsize_t sz)
{
struct vm_map_entry *tmp, *res;
tmp = RBT_ROOT(uaddr_free_rbtree, free);
res = NULL;
while (tmp) {
if (tmp->fspace >= sz) {
res = tmp;
tmp = RBT_LEFT(uaddr_free_rbtree, tmp);
} else if (tmp->fspace < sz)
tmp = RBT_RIGHT(uaddr_free_rbtree, tmp);
}
return res;
}
#endif
static inline vaddr_t
uvm_addr_align_forward(vaddr_t addr, vaddr_t align, vaddr_t offset)
{
vaddr_t adjusted;
KASSERT(offset < align || (align == 0 && offset == 0));
KASSERT((align & (align - 1)) == 0);
KASSERT((offset & PAGE_MASK) == 0);
align = MAX(align, PAGE_SIZE);
adjusted = addr & ~(align - 1);
adjusted += offset;
return (adjusted < addr ? adjusted + align : adjusted);
}
static inline vaddr_t
uvm_addr_align_backward(vaddr_t addr, vaddr_t align, vaddr_t offset)
{
vaddr_t adjusted;
KASSERT(offset < align || (align == 0 && offset == 0));
KASSERT((align & (align - 1)) == 0);
KASSERT((offset & PAGE_MASK) == 0);
align = MAX(align, PAGE_SIZE);
adjusted = addr & ~(align - 1);
adjusted += offset;
return (adjusted > addr ? adjusted - align : adjusted);
}
int
uvm_addr_fitspace(vaddr_t *min_result, vaddr_t *max_result,
vaddr_t low_addr, vaddr_t high_addr, vsize_t sz,
vaddr_t align, vaddr_t offset,
vsize_t before_gap, vsize_t after_gap)
{
vaddr_t tmp;
vsize_t fspace;
if (low_addr > high_addr)
return ENOMEM;
fspace = high_addr - low_addr;
if (fspace < before_gap + after_gap)
return ENOMEM;
if (fspace - before_gap - after_gap < sz)
return ENOMEM;
low_addr += before_gap;
low_addr = uvm_addr_align_forward(tmp = low_addr, align, offset);
if (low_addr < tmp)
return ENOMEM;
if (high_addr - after_gap - sz < low_addr)
return ENOMEM;
high_addr -= after_gap + sz;
high_addr = uvm_addr_align_backward(tmp = high_addr, align, offset);
if (high_addr > tmp)
return ENOMEM;
if (low_addr > high_addr)
return ENOMEM;
*min_result = low_addr;
*max_result = high_addr;
return 0;
}
void
uvm_addr_init(void)
{
pool_init(&uaddr_pool, sizeof(struct uvm_addr_state), 0,
IPL_VM, PR_WAITOK, "uaddr", NULL);
pool_init(&uaddr_bestfit_pool, sizeof(struct uaddr_bestfit_state), 0,
IPL_VM, PR_WAITOK, "uaddrbest", NULL);
pool_init(&uaddr_pivot_pool, sizeof(struct uaddr_pivot_state), 0,
IPL_VM, PR_WAITOK, "uaddrpivot", NULL);
pool_init(&uaddr_rnd_pool, sizeof(struct uaddr_rnd_state), 0,
IPL_VM, PR_WAITOK, "uaddrrnd", NULL);
uaddr_kbootstrap.uaddr_minaddr = PAGE_SIZE;
uaddr_kbootstrap.uaddr_maxaddr = -(vaddr_t)PAGE_SIZE;
uaddr_kbootstrap.uaddr_functions = &uaddr_kernel_functions;
}
void
uvm_addr_destroy(struct uvm_addr_state *uaddr)
{
if (uaddr)
(*uaddr->uaddr_functions->uaddr_destroy)(uaddr);
}
int
uvm_addr_linsearch(struct vm_map *map, struct uvm_addr_state *uaddr,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vaddr_t hint, vsize_t sz, vaddr_t align, vaddr_t offset,
int direction, vaddr_t low, vaddr_t high,
vsize_t before_gap, vsize_t after_gap)
{
struct vm_map_entry *entry;
vaddr_t low_addr, high_addr;
KASSERT(entry_out != NULL && addr_out != NULL);
KASSERT(direction == -1 || direction == 1);
KASSERT((hint & PAGE_MASK) == 0 && (high & PAGE_MASK) == 0 &&
(low & PAGE_MASK) == 0 &&
(before_gap & PAGE_MASK) == 0 && (after_gap & PAGE_MASK) == 0);
KASSERT(high + sz > high);
if (hint == 0)
hint = (direction == 1 ? low : high);
else if (hint > high) {
if (direction != -1)
return ENOMEM;
hint = high;
} else if (hint < low) {
if (direction != 1)
return ENOMEM;
hint = low;
}
for (entry = uvm_map_entrybyaddr(&map->addr,
hint - (direction == -1 ? 1 : 0)); entry != NULL;
entry = (direction == 1 ?
RBT_NEXT(uvm_map_addr, entry) :
RBT_PREV(uvm_map_addr, entry))) {
if ((direction == 1 && VMMAP_FREE_START(entry) > high) ||
(direction == -1 && VMMAP_FREE_END(entry) < low)) {
break;
}
if (uvm_addr_fitspace(&low_addr, &high_addr,
MAX(low, VMMAP_FREE_START(entry)),
MIN(high, VMMAP_FREE_END(entry)),
sz, align, offset, before_gap, after_gap) == 0) {
*entry_out = entry;
if (hint >= low_addr && hint <= high_addr) {
*addr_out = hint;
} else {
*addr_out = (direction == 1 ?
low_addr : high_addr);
}
return 0;
}
}
return ENOMEM;
}
int
uvm_addr_invoke(struct vm_map *map, struct uvm_addr_state *uaddr,
struct vm_map_entry **entry_out, struct vm_map_entry **last_out,
vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset, vm_prot_t prot, vaddr_t hint)
{
int error;
if (uaddr == NULL)
return ENOMEM;
hint &= ~((vaddr_t)PAGE_MASK);
if (hint != 0 &&
!(hint >= uaddr->uaddr_minaddr && hint < uaddr->uaddr_maxaddr))
return ENOMEM;
vm_map_assert_anylock(map);
error = (*uaddr->uaddr_functions->uaddr_select)(map, uaddr,
entry_out, addr_out, sz, align, offset, prot, hint);
if (error == 0) {
KASSERT(*entry_out != NULL);
*last_out = NULL;
if (!uvm_map_isavail(map, uaddr, entry_out, last_out,
*addr_out, sz)) {
panic("uvm_addr_invoke: address selector %p "
"(%s 0x%lx-0x%lx) "
"returned unavailable address 0x%lx sz 0x%lx",
uaddr, uaddr->uaddr_functions->uaddr_name,
uaddr->uaddr_minaddr, uaddr->uaddr_maxaddr,
*addr_out, sz);
}
}
return error;
}
#if defined(DEBUG) || defined(DDB)
void
uvm_addr_print(struct uvm_addr_state *uaddr, const char *slot, boolean_t full,
int (*pr)(const char *, ...))
{
if (uaddr == NULL) {
(*pr)("- uvm_addr %s: NULL\n", slot);
return;
}
(*pr)("- uvm_addr %s: %p (%s 0x%lx-0x%lx)\n", slot, uaddr,
uaddr->uaddr_functions->uaddr_name,
uaddr->uaddr_minaddr, uaddr->uaddr_maxaddr);
if (uaddr->uaddr_functions->uaddr_print == NULL)
return;
(*uaddr->uaddr_functions->uaddr_print)(uaddr, full, pr);
}
#endif
void
uaddr_destroy(struct uvm_addr_state *uaddr)
{
pool_put(&uaddr_pool, uaddr);
}
#if 0
const struct uvm_addr_functions uaddr_lin_functions = {
.uaddr_select = &uaddr_lin_select,
.uaddr_destroy = &uaddr_destroy,
.uaddr_name = "uaddr_lin"
};
struct uvm_addr_state *
uaddr_lin_create(vaddr_t minaddr, vaddr_t maxaddr)
{
struct uvm_addr_state *uaddr;
uaddr = pool_get(&uaddr_pool, PR_WAITOK);
uaddr->uaddr_minaddr = minaddr;
uaddr->uaddr_maxaddr = maxaddr;
uaddr->uaddr_functions = &uaddr_lin_functions;
return uaddr;
}
int
uaddr_lin_select(struct vm_map *map, struct uvm_addr_state *uaddr,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset,
vm_prot_t prot, vaddr_t hint)
{
vaddr_t guard_sz;
guard_sz = ((map->flags & VM_MAP_GUARDPAGES) == 0 ? 0 : PAGE_SIZE);
if (uaddr->uaddr_maxaddr - uaddr->uaddr_minaddr - guard_sz < sz)
return ENOMEM;
return uvm_addr_linsearch(map, uaddr, entry_out, addr_out, 0, sz,
align, offset, 1, uaddr->uaddr_minaddr, uaddr->uaddr_maxaddr - sz,
0, guard_sz);
}
#endif
const struct uvm_addr_functions uaddr_rnd_functions = {
.uaddr_select = &uaddr_rnd_select,
.uaddr_free_insert = &uaddr_rnd_insert,
.uaddr_free_remove = &uaddr_rnd_remove,
.uaddr_destroy = &uaddr_rnd_destroy,
#if defined(DEBUG) || defined(DDB)
#if 0
.uaddr_print = &uaddr_rnd_print,
#endif
#endif
.uaddr_name = "uaddr_rnd"
};
struct uvm_addr_state *
uaddr_rnd_create(vaddr_t minaddr, vaddr_t maxaddr)
{
struct uaddr_rnd_state *uaddr;
uaddr = pool_get(&uaddr_rnd_pool, PR_WAITOK);
uaddr->ur_uaddr.uaddr_minaddr = minaddr;
uaddr->ur_uaddr.uaddr_maxaddr = maxaddr;
uaddr->ur_uaddr.uaddr_functions = &uaddr_rnd_functions;
#if 0
TAILQ_INIT(&uaddr->ur_free);
#endif
return &uaddr->ur_uaddr;
}
int
uaddr_rnd_select(struct vm_map *map, struct uvm_addr_state *uaddr,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset,
vm_prot_t prot, vaddr_t hint)
{
struct vmspace *vm;
vaddr_t minaddr, maxaddr;
vaddr_t guard_sz;
vaddr_t low_addr, high_addr;
struct vm_map_entry *entry, *next;
vsize_t before_gap, after_gap;
vaddr_t tmp;
KASSERT((map->flags & VM_MAP_ISVMSPACE) != 0);
vm = (struct vmspace *)map;
guard_sz = ((map->flags & VM_MAP_GUARDPAGES) == 0 ? 0 : PAGE_SIZE);
if (uaddr->uaddr_maxaddr - guard_sz < sz)
return ENOMEM;
minaddr = uvm_addr_align_forward(uaddr->uaddr_minaddr, align, offset);
maxaddr = uvm_addr_align_backward(uaddr->uaddr_maxaddr - sz - guard_sz,
align, offset);
if (minaddr >= maxaddr)
return ENOMEM;
if (hint == 0)
hint = uvm_map_hint(vm, prot, minaddr, maxaddr);
hint = MIN(MAX(hint, minaddr), maxaddr);
tmp = hint;
hint = uvm_addr_align_forward(tmp, align, offset);
if (hint < tmp || hint > maxaddr)
return ENOMEM;
before_gap = 0;
after_gap = guard_sz;
hint -= MIN(hint, before_gap);
entry = uvm_map_entrybyaddr(&map->addr, hint);
while (entry != NULL &&
entry->fspace_augment < before_gap + after_gap + sz)
entry = RBT_PARENT(uvm_map_addr, entry);
while (entry != NULL) {
if (VMMAP_FREE_END(entry) > hint &&
uvm_map_uaddr_e(map, entry) == uaddr &&
uvm_addr_fitspace(&low_addr, &high_addr,
MAX(uaddr->uaddr_minaddr, VMMAP_FREE_START(entry)),
MIN(uaddr->uaddr_maxaddr, VMMAP_FREE_END(entry)),
sz, align, offset, before_gap, after_gap) == 0) {
*entry_out = entry;
if (hint >= low_addr && hint <= high_addr)
*addr_out = hint;
else
*addr_out = low_addr;
return 0;
}
next = RBT_RIGHT(uvm_map_addr, entry);
if (next != NULL &&
next->fspace_augment >= before_gap + after_gap + sz) {
entry = next;
while ((next = RBT_LEFT(uvm_map_addr, entry)) !=
NULL)
entry = next;
} else {
do_parent:
next = RBT_PARENT(uvm_map_addr, entry);
if (next == NULL)
entry = NULL;
else if (RBT_LEFT(uvm_map_addr, next) == entry)
entry = next;
else {
entry = next;
goto do_parent;
}
}
}
return ENOMEM;
}
void
uaddr_rnd_destroy(struct uvm_addr_state *uaddr)
{
pool_put(&uaddr_rnd_pool, uaddr);
}
void
uaddr_rnd_insert(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry *entry)
{
return;
}
void
uaddr_rnd_remove(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry *entry)
{
return;
}
#if 0
#if defined(DEBUG) || defined(DDB)
void
uaddr_rnd_print(struct uvm_addr_state *uaddr_p, boolean_t full,
int (*pr)(const char*, ...))
{
struct vm_map_entry *entry;
struct uaddr_rnd_state *uaddr;
vaddr_t addr;
size_t count;
vsize_t space;
uaddr = (struct uaddr_rnd_state *)uaddr_p;
addr = 0;
count = 0;
space = 0;
TAILQ_FOREACH(entry, &uaddr->ur_free, dfree.tailq) {
count++;
space += entry->fspace;
if (full) {
(*pr)("\tentry %p: 0x%lx-0x%lx G=0x%lx F=0x%lx\n",
entry, entry->start, entry->end,
entry->guard, entry->fspace);
(*pr)("\t\tfree: 0x%lx-0x%lx\n",
VMMAP_FREE_START(entry), VMMAP_FREE_END(entry));
}
if (entry->start < addr) {
if (!full)
(*pr)("\tentry %p: 0x%lx-0x%lx "
"G=0x%lx F=0x%lx\n",
entry, entry->start, entry->end,
entry->guard, entry->fspace);
(*pr)("\t\tstart=0x%lx, expected at least 0x%lx\n",
entry->start, addr);
}
addr = VMMAP_FREE_END(entry);
}
(*pr)("\t0x%lu entries, 0x%lx free bytes\n", count, space);
}
#endif
#endif
const struct uvm_addr_functions uaddr_kernel_functions = {
.uaddr_select = &uaddr_kbootstrap_select,
.uaddr_destroy = &uaddr_kbootstrap_destroy,
.uaddr_name = "uaddr_kbootstrap"
};
int
uaddr_kbootstrap_select(struct vm_map *map, struct uvm_addr_state *uaddr,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset, vm_prot_t prot, vaddr_t hint)
{
vaddr_t tmp;
RBT_FOREACH(*entry_out, uvm_map_addr, &map->addr) {
if (VMMAP_FREE_END(*entry_out) <= uvm_maxkaddr &&
uvm_addr_fitspace(addr_out, &tmp,
VMMAP_FREE_START(*entry_out), VMMAP_FREE_END(*entry_out),
sz, align, offset, 0, 0) == 0)
return 0;
}
return ENOMEM;
}
void
uaddr_kbootstrap_destroy(struct uvm_addr_state *uaddr)
{
KASSERT(uaddr == (struct uvm_addr_state *)&uaddr_kbootstrap);
}
#ifndef SMALL_KERNEL
const struct uvm_addr_functions uaddr_bestfit_functions = {
.uaddr_select = &uaddr_bestfit_select,
.uaddr_free_insert = &uaddr_bestfit_insert,
.uaddr_free_remove = &uaddr_bestfit_remove,
.uaddr_destroy = &uaddr_bestfit_destroy,
.uaddr_name = "uaddr_bestfit"
};
struct uvm_addr_state *
uaddr_bestfit_create(vaddr_t minaddr, vaddr_t maxaddr)
{
struct uaddr_bestfit_state *uaddr;
uaddr = pool_get(&uaddr_bestfit_pool, PR_WAITOK);
uaddr->ubf_uaddr.uaddr_minaddr = minaddr;
uaddr->ubf_uaddr.uaddr_maxaddr = maxaddr;
uaddr->ubf_uaddr.uaddr_functions = &uaddr_bestfit_functions;
RBT_INIT(uaddr_free_rbtree, &uaddr->ubf_free);
return &uaddr->ubf_uaddr;
}
void
uaddr_bestfit_destroy(struct uvm_addr_state *uaddr)
{
pool_put(&uaddr_bestfit_pool, uaddr);
}
void
uaddr_bestfit_insert(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry *entry)
{
struct uaddr_bestfit_state *uaddr;
struct vm_map_entry *rb_rv;
uaddr = (struct uaddr_bestfit_state *)uaddr_p;
if ((rb_rv = RBT_INSERT(uaddr_free_rbtree, &uaddr->ubf_free, entry)) !=
NULL) {
panic("%s: duplicate insertion: state %p "
"inserting %p, colliding with %p", __func__,
uaddr, entry, rb_rv);
}
}
void
uaddr_bestfit_remove(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry *entry)
{
struct uaddr_bestfit_state *uaddr;
uaddr = (struct uaddr_bestfit_state *)uaddr_p;
if (RBT_REMOVE(uaddr_free_rbtree, &uaddr->ubf_free, entry) != entry)
panic("%s: entry was not in tree", __func__);
}
int
uaddr_bestfit_select(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset,
vm_prot_t prot, vaddr_t hint)
{
vaddr_t min, max;
struct uaddr_bestfit_state *uaddr;
struct vm_map_entry *entry;
vsize_t guardsz;
uaddr = (struct uaddr_bestfit_state *)uaddr_p;
guardsz = ((map->flags & VM_MAP_GUARDPAGES) ? PAGE_SIZE : 0);
if (sz + guardsz < sz)
return ENOMEM;
entry = uvm_addr_entrybyspace(&uaddr->ubf_free, sz + guardsz);
if (entry == NULL)
return ENOMEM;
while (uvm_addr_fitspace(&min, &max,
VMMAP_FREE_START(entry), VMMAP_FREE_END(entry),
sz, align, offset, 0, guardsz) != 0) {
entry = RBT_NEXT(uaddr_free_rbtree, entry);
if (entry == NULL)
return ENOMEM;
}
*entry_out = entry;
*addr_out = (min - VMMAP_FREE_START(entry) <=
VMMAP_FREE_END(entry) - guardsz - sz - max ?
min : max);
return 0;
}
#endif
#ifndef SMALL_KERNEL
const struct uvm_addr_functions uaddr_pivot_functions = {
.uaddr_select = &uaddr_pivot_select,
.uaddr_free_insert = &uaddr_pivot_insert,
.uaddr_free_remove = &uaddr_pivot_remove,
.uaddr_destroy = &uaddr_pivot_destroy,
#if defined(DEBUG) || defined(DDB)
.uaddr_print = &uaddr_pivot_print,
#endif
.uaddr_name = "uaddr_pivot"
};
vsize_t
uaddr_pivot_random(void)
{
int r;
r = arc4random_uniform(PIVOT_RND) + arc4random_uniform(PIVOT_RND) -
(PIVOT_RND - 1);
if (r < 0)
r = -r;
return (vaddr_t)(1 + r) << PAGE_SHIFT;
}
int
uaddr_pivot_newpivot(struct vm_map *map, struct uaddr_pivot_state *uaddr,
struct uaddr_pivot *pivot,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset,
vsize_t before_gap, vsize_t after_gap)
{
struct vm_map_entry *entry, *found;
vaddr_t minaddr, maxaddr;
vsize_t dist;
vaddr_t found_minaddr, found_maxaddr;
vaddr_t min, max;
vsize_t arc4_arg;
int fit_error;
u_int32_t path;
minaddr = uaddr->up_uaddr.uaddr_minaddr;
maxaddr = uaddr->up_uaddr.uaddr_maxaddr;
KASSERT(minaddr < maxaddr);
#ifdef DIAGNOSTIC
if (minaddr + 2 * PAGE_SIZE > maxaddr) {
panic("uaddr_pivot_newpivot: cannot grant random pivot "
"in area less than 2 pages (size = 0x%lx)",
maxaddr - minaddr);
}
#endif
dist = MAX((maxaddr - minaddr) / 32, 2 * (vaddr_t)PAGE_SIZE);
if (dist >> PAGE_SHIFT > 0xffffffff) {
minaddr += (vsize_t)arc4random() << PAGE_SHIFT;
maxaddr -= (vsize_t)arc4random() << PAGE_SHIFT;
} else {
minaddr += (vsize_t)arc4random_uniform(dist >> PAGE_SHIFT) <<
PAGE_SHIFT;
maxaddr -= (vsize_t)arc4random_uniform(dist >> PAGE_SHIFT) <<
PAGE_SHIFT;
}
path = arc4random();
found = NULL;
entry = RBT_ROOT(uaddr_free_rbtree, &uaddr->up_free);
while (entry != NULL) {
fit_error = uvm_addr_fitspace(&min, &max,
MAX(VMMAP_FREE_START(entry), minaddr),
MIN(VMMAP_FREE_END(entry), maxaddr),
sz, align, offset, before_gap, after_gap);
if (fit_error == 0) {
found = entry;
found_minaddr = min;
found_maxaddr = max;
}
if (fit_error != 0)
entry = RBT_RIGHT(uaddr_free_rbtree, entry);
else if ((path & 0x1) == 0) {
path >>= 1;
entry = RBT_RIGHT(uaddr_free_rbtree, entry);
} else {
path >>= 1;
entry = RBT_LEFT(uaddr_free_rbtree, entry);
}
}
if (found == NULL)
return ENOMEM;
if (found_maxaddr == found_minaddr)
*addr_out = found_minaddr;
else {
KASSERT(align >= PAGE_SIZE && (align & (align - 1)) == 0);
arc4_arg = found_maxaddr - found_minaddr;
if (arc4_arg > 0xffffffff) {
*addr_out = found_minaddr +
(arc4random() & ~(align - 1));
} else {
*addr_out = found_minaddr +
(arc4random_uniform(arc4_arg) & ~(align - 1));
}
}
*entry_out = found;
pivot->entry = found;
pivot->dir = (arc4random() & 0x1 ? 1 : -1);
if (pivot->dir > 0)
pivot->addr = *addr_out + sz;
else
pivot->addr = *addr_out;
pivot->expire = PIVOT_EXPIRE - 1;
return 0;
}
int
uaddr_pivot_select(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset,
vm_prot_t prot, vaddr_t hint)
{
struct uaddr_pivot_state *uaddr;
struct vm_map_entry *entry;
struct uaddr_pivot *pivot;
vaddr_t min, max;
vsize_t before_gap, after_gap;
int err;
if (hint != 0) {
if (uaddr_rnd_select(map, uaddr_p, entry_out, addr_out,
sz, align, offset, prot, hint) == 0)
return 0;
return ENOMEM;
}
uaddr = (struct uaddr_pivot_state *)uaddr_p;
pivot = &uaddr->up_pivots[
arc4random_uniform(nitems(uaddr->up_pivots))];
before_gap = uaddr_pivot_random();
after_gap = uaddr_pivot_random();
if (pivot->addr == 0 || pivot->entry == NULL || pivot->expire == 0)
goto expired;
entry = pivot->entry;
if (pivot->dir > 0) {
if (uvm_addr_fitspace(&min, &max,
MAX(VMMAP_FREE_START(entry), pivot->addr),
VMMAP_FREE_END(entry), sz, align, offset,
before_gap, after_gap) == 0) {
*addr_out = min;
*entry_out = entry;
pivot->addr = min + sz;
pivot->expire--;
return 0;
}
} else {
if (uvm_addr_fitspace(&min, &max,
VMMAP_FREE_START(entry),
MIN(VMMAP_FREE_END(entry), pivot->addr),
sz, align, offset, before_gap, after_gap) == 0) {
*addr_out = max;
*entry_out = entry;
pivot->addr = max;
pivot->expire--;
return 0;
}
}
expired:
err = uaddr_pivot_newpivot(map, uaddr, pivot, entry_out, addr_out,
sz, align, offset, before_gap, after_gap);
return err;
}
void
uaddr_pivot_destroy(struct uvm_addr_state *uaddr)
{
pool_put(&uaddr_pivot_pool, uaddr);
}
void
uaddr_pivot_insert(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry *entry)
{
struct uaddr_pivot_state *uaddr;
struct vm_map_entry *rb_rv;
struct uaddr_pivot *p;
vaddr_t check_addr;
vaddr_t start, end;
uaddr = (struct uaddr_pivot_state *)uaddr_p;
if ((rb_rv = RBT_INSERT(uaddr_free_rbtree, &uaddr->up_free, entry)) !=
NULL) {
panic("%s: duplicate insertion: state %p "
"inserting entry %p which collides with %p", __func__,
uaddr, entry, rb_rv);
}
start = VMMAP_FREE_START(entry);
end = VMMAP_FREE_END(entry);
for (p = &uaddr->up_pivots[0];
p != &uaddr->up_pivots[nitems(uaddr->up_pivots)]; p++) {
check_addr = p->addr;
if (check_addr == 0)
continue;
if (p->dir < 0)
check_addr--;
if (start <= check_addr &&
check_addr < end) {
KASSERT(p->entry == NULL);
p->entry = entry;
}
}
}
void
uaddr_pivot_remove(struct vm_map *map, struct uvm_addr_state *uaddr_p,
struct vm_map_entry *entry)
{
struct uaddr_pivot_state *uaddr;
struct uaddr_pivot *p;
uaddr = (struct uaddr_pivot_state *)uaddr_p;
if (RBT_REMOVE(uaddr_free_rbtree, &uaddr->up_free, entry) != entry)
panic("%s: entry was not in tree", __func__);
for (p = &uaddr->up_pivots[0];
p != &uaddr->up_pivots[nitems(uaddr->up_pivots)]; p++) {
if (p->entry == entry)
p->entry = NULL;
}
}
struct uvm_addr_state *
uaddr_pivot_create(vaddr_t minaddr, vaddr_t maxaddr)
{
struct uaddr_pivot_state *uaddr;
uaddr = pool_get(&uaddr_pivot_pool, PR_WAITOK);
uaddr->up_uaddr.uaddr_minaddr = minaddr;
uaddr->up_uaddr.uaddr_maxaddr = maxaddr;
uaddr->up_uaddr.uaddr_functions = &uaddr_pivot_functions;
RBT_INIT(uaddr_free_rbtree, &uaddr->up_free);
memset(uaddr->up_pivots, 0, sizeof(uaddr->up_pivots));
return &uaddr->up_uaddr;
}
#if defined(DEBUG) || defined(DDB)
void
uaddr_pivot_print(struct uvm_addr_state *uaddr_p, boolean_t full,
int (*pr)(const char *, ...))
{
struct uaddr_pivot_state *uaddr;
struct uaddr_pivot *pivot;
struct vm_map_entry *entry;
int i;
vaddr_t check_addr;
uaddr = (struct uaddr_pivot_state *)uaddr_p;
for (i = 0; i < NUM_PIVOTS; i++) {
pivot = &uaddr->up_pivots[i];
(*pr)("\tpivot 0x%lx, epires in %d, direction %d\n",
pivot->addr, pivot->expire, pivot->dir);
}
if (!full)
return;
if (RBT_EMPTY(uaddr_free_rbtree, &uaddr->up_free))
(*pr)("\tempty\n");
RBT_FOREACH(entry, uaddr_free_rbtree, &uaddr->up_free) {
(*pr)("\t0x%lx - 0x%lx free (0x%lx bytes)\n",
VMMAP_FREE_START(entry), VMMAP_FREE_END(entry),
VMMAP_FREE_END(entry) - VMMAP_FREE_START(entry));
for (i = 0; i < NUM_PIVOTS; i++) {
pivot = &uaddr->up_pivots[i];
check_addr = pivot->addr;
if (check_addr == 0)
continue;
if (pivot->dir < 0)
check_addr--;
if (VMMAP_FREE_START(entry) <= check_addr &&
check_addr < VMMAP_FREE_END(entry)) {
(*pr)("\t\tcontains pivot %d (0x%lx)\n",
i, pivot->addr);
}
}
}
}
#endif
#endif
#ifndef SMALL_KERNEL
const struct uvm_addr_functions uaddr_stack_brk_functions = {
.uaddr_select = &uaddr_stack_brk_select,
.uaddr_destroy = &uaddr_destroy,
.uaddr_name = "uaddr_stckbrk"
};
int
uaddr_stack_brk_select(struct vm_map *map, struct uvm_addr_state *uaddr,
struct vm_map_entry **entry_out, vaddr_t *addr_out,
vsize_t sz, vaddr_t align, vaddr_t offset,
vm_prot_t prot, vaddr_t hint)
{
vaddr_t start;
vaddr_t end;
vsize_t before_gap;
vsize_t after_gap;
int dir;
start = MAX(map->b_start, uaddr->uaddr_minaddr);
end = MIN(map->b_end, uaddr->uaddr_maxaddr);
before_gap = 0;
after_gap = 0;
dir = -1;
if (end - start >= sz) {
if (uvm_addr_linsearch(map, uaddr, entry_out, addr_out,
0, sz, align, offset, dir, start, end - sz,
before_gap, after_gap) == 0)
return 0;
}
start = MAX(map->s_start, uaddr->uaddr_minaddr);
end = MIN(map->s_end, uaddr->uaddr_maxaddr);
before_gap = ((arc4random() & 0x3) + 1) << PAGE_SHIFT;
after_gap = ((arc4random() & 0x3) + 1) << PAGE_SHIFT;
#ifdef MACHINE_STACK_GROWS_UP
dir = -1;
#else
dir = 1;
#endif
if (end - start >= before_gap + after_gap &&
end - start - before_gap - after_gap >= sz) {
if (uvm_addr_linsearch(map, uaddr, entry_out, addr_out,
0, sz, align, offset, dir, start, end - sz,
before_gap, after_gap) == 0)
return 0;
}
return ENOMEM;
}
struct uvm_addr_state *
uaddr_stack_brk_create(vaddr_t minaddr, vaddr_t maxaddr)
{
struct uvm_addr_state* uaddr;
uaddr = pool_get(&uaddr_pool, PR_WAITOK);
uaddr->uaddr_minaddr = minaddr;
uaddr->uaddr_maxaddr = maxaddr;
uaddr->uaddr_functions = &uaddr_stack_brk_functions;
return uaddr;
}
#endif
#ifndef SMALL_KERNEL
static inline int
uvm_mapent_fspace_cmp(const struct vm_map_entry *e1,
const struct vm_map_entry *e2)
{
if (e1->fspace != e2->fspace)
return (e1->fspace < e2->fspace ? -1 : 1);
return (e1->start < e2->start ? -1 : e1->start > e2->start);
}
RBT_GENERATE(uaddr_free_rbtree, vm_map_entry, dfree.rbtree,
uvm_mapent_fspace_cmp);
#endif