blob: 1e1d2fd9a5458506f1b067da9194ba8699b32027 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//
// Derived from zircon vm_aspace.
#include "simple_allocator.h"
#include "magma_util/dlog.h"
#include "magma_util/macros.h"
#include <limits.h> // PAGE_SIZE
#include <memory>
#if PAGE_SIZE == 4096
#define PAGE_SIZE_POW2 12
#else
#error Must define PAGE_SIZE_POW2
#endif
namespace magma {
// Returns true if the gap is good and addr_out is set.
// Otherwise false is returned and continue_search should be checked.
bool SimpleAllocator::CheckGap(SimpleAllocator::Region* prev, SimpleAllocator::Region* next,
uint64_t align, size_t size, uint64_t* addr_out,
bool* continue_search_out)
{
DASSERT(addr_out);
DASSERT(continue_search_out);
uint64_t gap_begin = prev ? (prev->base + prev->size) : base();
uint64_t gap_end; // last byte of a gap
if (next) {
if (gap_begin == next->base) {
*continue_search_out = true;
return false;
}
gap_end = next->base - 1;
} else {
if (gap_begin == base() + this->size()) {
*continue_search_out = false;
return false;
}
gap_end = base() + this->size() - 1;
}
*addr_out = magma::round_up(gap_begin, align);
if (*addr_out < gap_begin) {
*continue_search_out = false;
return false;
}
if (*addr_out < gap_end && ((gap_end - *addr_out + 1) >= size))
return true;
*continue_search_out = true;
return false;
}
//////////////////////////////////////////////////////////////////////////////
SimpleAllocator::Region::Region(uint64_t base_in, size_t size_in) : base(base_in), size(size_in)
{
DASSERT(size > 0);
DASSERT(base + size - 1 >= base);
}
std::unique_ptr<SimpleAllocator> SimpleAllocator::Create(uint64_t base, size_t size)
{
return std::unique_ptr<SimpleAllocator>(new SimpleAllocator(base, size));
}
SimpleAllocator::SimpleAllocator(uint64_t base, size_t size) : AddressSpaceAllocator(base, size) {}
bool SimpleAllocator::Alloc(size_t size, uint8_t align_pow2, uint64_t* addr_out)
{
DLOG("Alloc size 0x%zx align_pow2 0x%x", size, align_pow2);
DASSERT(addr_out);
size = magma::round_up(size, PAGE_SIZE);
if (size == 0)
return DRETF(false, "can't allocate size zero");
DASSERT(magma::is_page_aligned(size));
if (align_pow2 < PAGE_SIZE_POW2)
align_pow2 = PAGE_SIZE_POW2;
uint64_t align = 1UL << align_pow2;
uint64_t addr;
bool continue_search;
// try to pick spot at the beginning of address space
if (CheckGap(nullptr, regions_.empty() ? nullptr : &regions_.front(), align, size, &addr,
&continue_search)) {
*addr_out = addr;
regions_.emplace_front(addr, size);
DLOG("allocated addr 0x%lx", addr);
return true;
}
// search the middle of the list
for (auto iter = regions_.begin(); continue_search && iter != regions_.end();) {
auto prev = &(*iter);
auto next = (++iter == regions_.end()) ? nullptr : &(*iter);
if (CheckGap(prev, next, align, size, &addr, &continue_search)) {
*addr_out = addr;
regions_.insert(iter, Region(addr, size));
DLOG("allocated addr 0x%lx", addr);
return true;
}
}
return DRETF(false, "failed to alloc");
}
bool SimpleAllocator::Free(uint64_t addr)
{
DLOG("Free addr 0x%lx", addr);
auto iter = FindRegion(addr);
if (iter == regions_.end())
return DRETF(false, "couldn't find region to free");
regions_.erase(iter);
return true;
}
bool SimpleAllocator::GetSize(uint64_t addr, size_t* size_out)
{
auto iter = FindRegion(addr);
if (iter == regions_.end())
return DRETF(false, "couldn't find region");
*size_out = iter->size;
return true;
}
std::list<SimpleAllocator::Region>::iterator SimpleAllocator::FindRegion(uint64_t addr)
{
for (auto iter = regions_.begin(); iter != regions_.end(); iter++) {
auto region = *iter;
if ((addr >= region.base) && (addr <= region.base + region.size - 1))
return iter;
}
return regions_.end();
}
} // namespace magma