#include <vm/vm.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <algorithm>
#include <OS.h>
#include <KernelExport.h>
#include <AutoDeleterDrivers.h>
#include <symbol_versioning.h>
#include <arch/cpu.h>
#include <arch/vm.h>
#include <arch/user_memory.h>
#include <boot/elf.h>
#include <boot/stage2.h>
#include <condition_variable.h>
#include <console.h>
#include <debug.h>
#include <file_cache.h>
#include <fs/fd.h>
#include <heap.h>
#include <kernel.h>
#include <interrupts.h>
#include <lock.h>
#include <low_resource_manager.h>
#include <slab/Slab.h>
#include <smp.h>
#include <system_info.h>
#include <thread.h>
#include <team.h>
#include <tracing.h>
#include <util/AutoLock.h>
#include <util/BitUtils.h>
#include <util/ThreadAutoLock.h>
#include <vm/vm_page.h>
#include <vm/vm_priv.h>
#include <vm/VMAddressSpace.h>
#include <vm/VMArea.h>
#include <vm/VMCache.h>
#include "VMAddressSpaceLocking.h"
#include "VMAnonymousCache.h"
#include "VMAnonymousNoSwapCache.h"
#include "IORequest.h"
#ifdef TRACE_VM
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
#ifdef TRACE_FAULTS
# define FTRACE(x) dprintf x
#else
# define FTRACE(x) ;
#endif
namespace {
class AreaCacheLocking {
public:
inline bool Lock(VMCache* lockable)
{
return false;
}
inline void Unlock(VMCache* lockable)
{
vm_area_put_locked_cache(lockable);
}
};
class AreaCacheLocker : public AutoLocker<VMCache, AreaCacheLocking> {
public:
inline AreaCacheLocker(VMCache* cache = NULL)
: AutoLocker<VMCache, AreaCacheLocking>(cache, true)
{
}
inline AreaCacheLocker(VMArea* area)
: AutoLocker<VMCache, AreaCacheLocking>()
{
SetTo(area);
}
inline void SetTo(VMCache* cache, bool alreadyLocked)
{
AutoLocker<VMCache, AreaCacheLocking>::SetTo(cache, alreadyLocked);
}
inline void SetTo(VMArea* area)
{
return AutoLocker<VMCache, AreaCacheLocking>::SetTo(
area != NULL ? vm_area_get_locked_cache(area) : NULL, true, true);
}
};
class VMCacheChainLocker {
public:
VMCacheChainLocker()
:
fTopCache(NULL),
fBottomCache(NULL)
{
}
VMCacheChainLocker(VMCache* topCache)
:
fTopCache(topCache),
fBottomCache(topCache)
{
}
~VMCacheChainLocker()
{
Unlock();
}
void SetTo(VMCache* topCache)
{
fTopCache = topCache;
fBottomCache = topCache;
if (topCache != NULL)
topCache->SetUserData(NULL);
}
VMCache* LockSourceCache()
{
if (fBottomCache == NULL || fBottomCache->source == NULL)
return NULL;
VMCache* previousCache = fBottomCache;
fBottomCache = fBottomCache->source;
fBottomCache->Lock();
fBottomCache->AcquireRefLocked();
fBottomCache->SetUserData(previousCache);
return fBottomCache;
}
void LockAllSourceCaches()
{
while (LockSourceCache() != NULL) {
}
}
void Unlock(VMCache* exceptCache = NULL)
{
if (fTopCache == NULL)
return;
VMCache* cache = fBottomCache;
while (cache != NULL) {
VMCache* nextCache = (VMCache*)cache->UserData();
if (cache != exceptCache)
cache->ReleaseRefAndUnlock(cache != fTopCache);
if (cache == fTopCache)
break;
cache = nextCache;
}
fTopCache = NULL;
fBottomCache = NULL;
}
void UnlockKeepRefs(bool keepTopCacheLocked)
{
if (fTopCache == NULL)
return;
VMCache* nextCache = fBottomCache;
VMCache* cache = NULL;
while (keepTopCacheLocked
? nextCache != fTopCache : cache != fTopCache) {
cache = nextCache;
nextCache = (VMCache*)cache->UserData();
cache->Unlock(cache != fTopCache);
}
}
void RelockCaches(bool topCacheLocked)
{
if (fTopCache == NULL)
return;
VMCache* nextCache = fTopCache;
VMCache* cache = NULL;
if (topCacheLocked) {
cache = nextCache;
nextCache = cache->source;
}
while (cache != fBottomCache && nextCache != NULL) {
VMCache* consumer = cache;
cache = nextCache;
nextCache = cache->source;
cache->Lock();
cache->SetUserData(consumer);
}
}
private:
VMCache* fTopCache;
VMCache* fBottomCache;
};
}
static const size_t kMemoryReserveForPriority[] = {
VM_MEMORY_RESERVE_USER,
VM_MEMORY_RESERVE_SYSTEM,
0
};
static ObjectCache** sPageMappingsObjectCaches;
static uint32 sPageMappingsMask;
static rw_lock sAreaCacheLock = RW_LOCK_INITIALIZER("area->cache");
static rw_spinlock sAvailableMemoryLock = B_RW_SPINLOCK_INITIALIZER;
static off_t sAvailableMemory;
#if ENABLE_SWAP_SUPPORT
static off_t sAvailableMemoryAndSwap;
#endif
static off_t sNeededMemory;
static uint32 sPageFaults;
static VMPhysicalPageMapper* sPhysicalPageMapper;
static void delete_area(VMAddressSpace* addressSpace, VMArea* area,
bool deletingAddressSpace, bool alreadyRemoved = false);
static status_t vm_soft_fault(VMAddressSpace* addressSpace, addr_t address,
bool isWrite, bool isExecute, bool isUser, vm_page** wirePage);
static status_t map_backing_store(VMAddressSpace* addressSpace,
VMCache* cache, off_t offset, const char* areaName, addr_t size, int wiring,
int protection, int protectionMax, int mapping, uint32 flags,
const virtual_address_restrictions* addressRestrictions, bool kernel,
VMArea** _area, void** _virtualAddress);
static status_t check_protection(team_id& team, uint32* protection);
#if VM_PAGE_FAULT_TRACING
namespace VMPageFaultTracing {
class PageFaultStart : public AbstractTraceEntry {
public:
PageFaultStart(addr_t address, bool write, bool user, addr_t pc)
:
fAddress(address),
fPC(pc),
fWrite(write),
fUser(user)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("page fault %#lx %s %s, pc: %#lx", fAddress,
fWrite ? "write" : "read", fUser ? "user" : "kernel", fPC);
}
private:
addr_t fAddress;
addr_t fPC;
bool fWrite;
bool fUser;
};
enum {
PAGE_FAULT_ERROR_NO_AREA = 0,
PAGE_FAULT_ERROR_KERNEL_ONLY,
PAGE_FAULT_ERROR_WRITE_PROTECTED,
PAGE_FAULT_ERROR_READ_PROTECTED,
PAGE_FAULT_ERROR_EXECUTE_PROTECTED,
PAGE_FAULT_ERROR_KERNEL_BAD_USER_MEMORY,
PAGE_FAULT_ERROR_NO_ADDRESS_SPACE
};
class PageFaultError : public AbstractTraceEntry {
public:
PageFaultError(area_id area, status_t error)
:
fArea(area),
fError(error)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
switch (fError) {
case PAGE_FAULT_ERROR_NO_AREA:
out.Print("page fault error: no area");
break;
case PAGE_FAULT_ERROR_KERNEL_ONLY:
out.Print("page fault error: area: %ld, kernel only", fArea);
break;
case PAGE_FAULT_ERROR_WRITE_PROTECTED:
out.Print("page fault error: area: %ld, write protected",
fArea);
break;
case PAGE_FAULT_ERROR_READ_PROTECTED:
out.Print("page fault error: area: %ld, read protected", fArea);
break;
case PAGE_FAULT_ERROR_EXECUTE_PROTECTED:
out.Print("page fault error: area: %ld, execute protected",
fArea);
break;
case PAGE_FAULT_ERROR_KERNEL_BAD_USER_MEMORY:
out.Print("page fault error: kernel touching bad user memory");
break;
case PAGE_FAULT_ERROR_NO_ADDRESS_SPACE:
out.Print("page fault error: no address space");
break;
default:
out.Print("page fault error: area: %ld, error: %s", fArea,
strerror(fError));
break;
}
}
private:
area_id fArea;
status_t fError;
};
class PageFaultDone : public AbstractTraceEntry {
public:
PageFaultDone(area_id area, VMCache* topCache, VMCache* cache,
vm_page* page)
:
fArea(area),
fTopCache(topCache),
fCache(cache),
fPage(page)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("page fault done: area: %ld, top cache: %p, cache: %p, "
"page: %p", fArea, fTopCache, fCache, fPage);
}
private:
area_id fArea;
VMCache* fTopCache;
VMCache* fCache;
vm_page* fPage;
};
}
# define TPF(x) new(std::nothrow) VMPageFaultTracing::x;
#else
# define TPF(x) ;
#endif
static void
create_page_mappings_object_caches()
{
const int32 numCPUs = smp_get_num_cpus();
int32 count = next_power_of_2(numCPUs);
if (count > numCPUs)
count >>= 1;
sPageMappingsMask = count - 1;
sPageMappingsObjectCaches = new object_cache*[count];
if (sPageMappingsObjectCaches == NULL)
panic("failed to allocate page mappings object_cache array");
for (int32 i = 0; i < count; i++) {
char name[32];
snprintf(name, sizeof(name), "page mappings %" B_PRId32, i);
object_cache* cache = create_object_cache_etc(name,
sizeof(vm_page_mapping), 0, 0, 64, 128, CACHE_LARGE_SLAB, NULL, NULL,
NULL, NULL);
if (cache == NULL)
panic("failed to create page mappings object_cache");
object_cache_set_minimum_reserve(cache, 1024);
sPageMappingsObjectCaches[i] = cache;
}
}
static object_cache*
page_mapping_object_cache_for(page_num_t page)
{
return sPageMappingsObjectCaches[page & sPageMappingsMask];
}
static vm_page_mapping*
allocate_page_mapping(page_num_t page, uint32 flags = 0)
{
return (vm_page_mapping*)object_cache_alloc(page_mapping_object_cache_for(page),
flags);
}
void
vm_free_page_mapping(page_num_t page, vm_page_mapping* mapping, uint32 flags)
{
object_cache_free(page_mapping_object_cache_for(page), mapping, flags);
}
static inline void
increment_page_wired_count(vm_page* page)
{
if (!page->IsMapped())
atomic_add(&gMappedPagesCount, 1);
page->IncrementWiredCount();
}
static inline void
decrement_page_wired_count(vm_page* page)
{
page->DecrementWiredCount();
if (!page->IsMapped())
atomic_add(&gMappedPagesCount, -1);
}
static inline addr_t
virtual_page_address(VMArea* area, vm_page* page)
{
return area->Base()
+ ((page->cache_offset << PAGE_SHIFT) - area->cache_offset);
}
static inline bool
is_page_in_area(VMArea* area, vm_page* page)
{
off_t pageCacheOffsetBytes = (off_t)(page->cache_offset << PAGE_SHIFT);
return pageCacheOffsetBytes >= area->cache_offset
&& pageCacheOffsetBytes < area->cache_offset + (off_t)area->Size();
}
static VMArea*
lookup_area(VMAddressSpace* addressSpace, area_id id)
{
VMAreas::ReadLock();
VMArea* area = VMAreas::LookupLocked(id);
if (area != NULL && area->address_space != addressSpace)
area = NULL;
VMAreas::ReadUnlock();
return area;
}
static inline size_t
area_page_protections_size(size_t areaSize)
{
return (areaSize / B_PAGE_SIZE + 1) / 2;
}
static status_t
allocate_area_page_protections(VMArea* area)
{
size_t bytes = area_page_protections_size(area->Size());
area->page_protections = (uint8*)malloc_etc(bytes,
area->address_space == VMAddressSpace::Kernel()
? HEAP_DONT_LOCK_KERNEL_SPACE : 0);
if (area->page_protections == NULL)
return B_NO_MEMORY;
uint32 areaProtection = area->protection
& (B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA);
memset(area->page_protections, areaProtection | (areaProtection << 4), bytes);
area->protection &= ~(B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA
| B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_KERNEL_EXECUTE_AREA);
return B_OK;
}
static inline uint8*
realloc_area_page_protections(uint8* pageProtections, size_t areaSize,
uint32 allocationFlags)
{
size_t bytes = area_page_protections_size(areaSize);
return (uint8*)realloc_etc(pageProtections, bytes, allocationFlags);
}
static inline void
set_area_page_protection(VMArea* area, addr_t pageAddress, uint32 protection)
{
protection &= B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA;
addr_t pageIndex = (pageAddress - area->Base()) / B_PAGE_SIZE;
uint8& entry = area->page_protections[pageIndex / 2];
if (pageIndex % 2 == 0)
entry = (entry & 0xf0) | protection;
else
entry = (entry & 0x0f) | (protection << 4);
}
static inline uint32
get_area_page_protection(VMArea* area, addr_t pageAddress)
{
if (area->page_protections == NULL)
return area->protection;
uint32 pageIndex = (pageAddress - area->Base()) / B_PAGE_SIZE;
uint32 protection = area->page_protections[pageIndex / 2];
if (pageIndex % 2 == 0)
protection &= 0x0f;
else
protection >>= 4;
uint32 kernelProtection = 0;
if ((protection & B_READ_AREA) != 0)
kernelProtection |= B_KERNEL_READ_AREA;
if ((protection & B_WRITE_AREA) != 0)
kernelProtection |= B_KERNEL_WRITE_AREA;
if (area->address_space == VMAddressSpace::Kernel())
return kernelProtection;
return protection | kernelProtection;
}
static inline size_t
compute_area_page_commitment(VMArea* area)
{
uint32 commitProtection = B_WRITE_AREA | B_KERNEL_WRITE_AREA;
if (area->cache->source == NULL)
commitProtection |= B_READ_AREA | B_KERNEL_READ_AREA;
if (area->page_protections == NULL) {
if ((area->protection & commitProtection) != 0)
return area->Size();
return area->cache->page_count * B_PAGE_SIZE;
}
const size_t bytes = area_page_protections_size(area->Size());
const bool oddPageCount = ((area->Size() / B_PAGE_SIZE) % 2) != 0;
size_t pages = 0;
for (size_t i = 0; i < bytes; i++) {
const uint8 protection = area->page_protections[i];
const off_t pageOffset = area->cache_offset + (i * 2 * B_PAGE_SIZE);
if (area->cache->LookupPage(pageOffset) != NULL
|| area->cache->StoreHasPage(pageOffset))
pages++;
else
pages += ((protection & (commitProtection << 0)) != 0) ? 1 : 0;
if (i == (bytes - 1) && oddPageCount)
break;
if (area->cache->LookupPage(pageOffset + B_PAGE_SIZE) != NULL
|| area->cache->StoreHasPage(pageOffset + B_PAGE_SIZE))
pages++;
else
pages += ((protection & (commitProtection << 4)) != 0) ? 1 : 0;
}
return pages;
}
static bool
is_area_only_cache_user(VMArea* area)
{
return area->cache->type == CACHE_TYPE_RAM
&& area->cache->areas.First() == area
&& area->cache->areas.GetNext(area) == NULL
&& area->cache->consumers.IsEmpty();
}
static status_t
map_page(VMArea* area, vm_page* page, addr_t address, uint32 protection,
vm_page_reservation* reservation)
{
VMTranslationMap* map = area->address_space->TranslationMap();
bool wasMapped = page->IsMapped();
if (area->wiring == B_NO_LOCK) {
DEBUG_PAGE_ACCESS_CHECK(page);
bool isKernelSpace = area->address_space == VMAddressSpace::Kernel();
vm_page_mapping* mapping = allocate_page_mapping(page->physical_page_number,
CACHE_DONT_WAIT_FOR_MEMORY
| (isKernelSpace ? CACHE_DONT_LOCK_KERNEL_SPACE : 0));
if (mapping == NULL)
return B_NO_MEMORY;
mapping->page = page;
mapping->area = area;
map->Lock();
map->Map(address, page->physical_page_number * B_PAGE_SIZE, protection,
area->MemoryType(), reservation);
if (!page->IsMapped())
atomic_add(&gMappedPagesCount, 1);
page->mappings.Add(mapping);
area->mappings.Add(mapping);
map->Unlock();
} else {
DEBUG_PAGE_ACCESS_CHECK(page);
map->Lock();
map->Map(address, page->physical_page_number * B_PAGE_SIZE, protection,
area->MemoryType(), reservation);
map->Unlock();
increment_page_wired_count(page);
}
if (!wasMapped) {
if (page->State() == PAGE_STATE_CACHED
|| page->State() == PAGE_STATE_INACTIVE) {
vm_page_set_state(page, PAGE_STATE_ACTIVE);
}
}
return B_OK;
}
static inline bool
unmap_page(VMArea* area, addr_t virtualAddress)
{
return area->address_space->TranslationMap()->UnmapPage(area,
virtualAddress, true);
}
static inline void
unmap_pages(VMArea* area, addr_t base, size_t size)
{
area->address_space->TranslationMap()->UnmapPages(area, base, size, true);
}
static inline bool
intersect_area(VMArea* area, addr_t& address, addr_t& size, addr_t& offset)
{
if (address < area->Base()) {
offset = area->Base() - address;
if (offset >= size)
return false;
address = area->Base();
size -= offset;
offset = 0;
if (size > area->Size())
size = area->Size();
return true;
}
offset = address - area->Base();
if (offset >= area->Size())
return false;
if (size >= area->Size() - offset)
size = area->Size() - offset;
return true;
}
static status_t
cut_area(VMAddressSpace* addressSpace, VMArea* area, addr_t address,
addr_t size, VMArea** _secondArea, bool kernel)
{
addr_t offset;
if (!intersect_area(area, address, size, offset))
return B_OK;
if (address == area->Base() && size == area->Size()) {
delete_area(addressSpace, area, false);
return B_OK;
}
int priority;
uint32 allocationFlags;
if (addressSpace == VMAddressSpace::Kernel()) {
priority = VM_PRIORITY_SYSTEM;
allocationFlags = HEAP_DONT_WAIT_FOR_MEMORY
| HEAP_DONT_LOCK_KERNEL_SPACE;
} else {
priority = VM_PRIORITY_USER;
allocationFlags = 0;
}
VMCache* cache = vm_area_get_locked_cache(area);
VMCacheChainLocker cacheChainLocker(cache);
cacheChainLocker.LockAllSourceCaches();
const bool onlyCacheUser = is_area_only_cache_user(area);
int resizePriority = priority;
const bool overcommitting = (area->protection & B_OVERCOMMITTING_AREA) != 0,
writable = (area->protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0;
if (onlyCacheUser && !overcommitting && (area->page_protections != NULL || !writable)) {
resizePriority = -1;
}
const addr_t oldSize = area->Size();
if (offset > 0 && size == (area->Size() - offset)) {
status_t error = addressSpace->ResizeArea(area, offset,
allocationFlags);
if (error != B_OK)
return error;
if (area->page_protections != NULL) {
uint8* newProtections = realloc_area_page_protections(
area->page_protections, area->Size(), allocationFlags);
if (newProtections == NULL) {
addressSpace->ResizeArea(area, oldSize, allocationFlags);
return B_NO_MEMORY;
}
area->page_protections = newProtections;
}
unmap_pages(area, address, size);
if (onlyCacheUser) {
cacheChainLocker.Unlock(cache);
cacheChainLocker.SetTo(cache);
status_t status = cache->Rebase(area->cache_offset, resizePriority);
ASSERT_ALWAYS(status == B_OK);
status = cache->Resize(area->cache_offset + area->Size(), resizePriority);
ASSERT_ALWAYS(status == B_OK);
}
if (resizePriority == -1) {
const size_t newCommitmentPages = compute_area_page_commitment(area);
cache->Commit(newCommitmentPages * B_PAGE_SIZE, priority);
}
return B_OK;
}
if (area->Base() == address) {
uint8* newProtections = NULL;
if (area->page_protections != NULL) {
newProtections = realloc_area_page_protections(NULL, area->Size(),
allocationFlags);
if (newProtections == NULL)
return B_NO_MEMORY;
}
status_t error = addressSpace->ShrinkAreaHead(area, area->Size() - size,
allocationFlags);
if (error != B_OK) {
free_etc(newProtections, allocationFlags);
return error;
}
if (area->page_protections != NULL) {
size_t oldBytes = area_page_protections_size(oldSize);
ssize_t pagesShifted = (oldSize - area->Size()) / B_PAGE_SIZE;
bitmap_shift<uint8>(area->page_protections, oldBytes * 8, -(pagesShifted * 4));
size_t bytes = area_page_protections_size(area->Size());
memcpy(newProtections, area->page_protections, bytes);
free_etc(area->page_protections, allocationFlags);
area->page_protections = newProtections;
}
unmap_pages(area, address, size);
if (onlyCacheUser) {
cacheChainLocker.Unlock(cache);
cacheChainLocker.SetTo(cache);
status_t status = cache->Rebase(area->cache_offset + size, resizePriority);
ASSERT_ALWAYS(status == B_OK);
status = cache->Resize(cache->virtual_base + area->Size(), resizePriority);
ASSERT_ALWAYS(status == B_OK);
}
area->cache_offset += size;
if (resizePriority == -1) {
const size_t newCommitmentPages = compute_area_page_commitment(area);
cache->Commit(newCommitmentPages * B_PAGE_SIZE, priority);
}
return B_OK;
}
const addr_t firstNewSize = offset;
const addr_t secondBase = address + size;
const addr_t secondSize = area->Size() - offset - size;
const off_t secondCacheOffset = area->cache_offset + (secondBase - area->Base());
unmap_pages(area, address, area->Size() - firstNewSize);
status_t error = addressSpace->ResizeArea(area, firstNewSize,
allocationFlags);
if (error != B_OK)
return error;
uint8* areaNewProtections = NULL;
uint8* secondAreaNewProtections = NULL;
if (area->page_protections != NULL) {
areaNewProtections = realloc_area_page_protections(NULL, area->Size(),
allocationFlags);
secondAreaNewProtections = realloc_area_page_protections(NULL, secondSize,
allocationFlags);
if (areaNewProtections == NULL || secondAreaNewProtections == NULL) {
addressSpace->ResizeArea(area, oldSize, allocationFlags);
free_etc(areaNewProtections, allocationFlags);
free_etc(secondAreaNewProtections, allocationFlags);
return B_NO_MEMORY;
}
}
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = (void*)secondBase;
addressRestrictions.address_specification = B_EXACT_ADDRESS;
VMArea* secondArea;
AutoLocker<VMCache> secondCacheLocker;
if (onlyCacheUser) {
VMCache* secondCache;
error = VMCacheFactory::CreateAnonymousCache(secondCache,
overcommitting, 0, 0,
dynamic_cast<VMAnonymousNoSwapCache*>(cache) == NULL, priority);
if (error != B_OK) {
addressSpace->ResizeArea(area, oldSize, allocationFlags);
free_etc(areaNewProtections, allocationFlags);
free_etc(secondAreaNewProtections, allocationFlags);
return error;
}
secondCache->Lock();
secondCacheLocker.SetTo(secondCache, true);
secondCache->temporary = cache->temporary;
secondCache->virtual_base = secondCacheOffset;
size_t commitmentStolen = 0;
if (!overcommitting && resizePriority != -1) {
const size_t steal = PAGE_ALIGN(secondSize);
if (cache->Commitment() > (off_t)steal) {
secondCache->TakeCommitmentFrom(cache, steal);
commitmentStolen = steal;
}
}
error = secondCache->Resize(secondCache->virtual_base + secondSize, resizePriority);
if (error == B_OK) {
if (cache->source != NULL)
cache->source->AddConsumer(secondCache);
error = secondCache->Adopt(cache, secondCache->virtual_base, secondSize,
secondCache->virtual_base);
}
if (error == B_OK) {
cacheChainLocker.Unlock(cache);
cacheChainLocker.SetTo(cache);
error = map_backing_store(addressSpace, secondCache,
secondCacheOffset, area->name, secondSize,
area->wiring, area->protection, area->protection_max,
REGION_NO_PRIVATE_MAP, CREATE_AREA_DONT_COMMIT_MEMORY,
&addressRestrictions, kernel, &secondArea, NULL);
}
if (error != B_OK) {
cache->TakeCommitmentFrom(secondCache, commitmentStolen);
status_t readoptStatus = cache->Adopt(secondCache,
secondCache->virtual_base, secondSize, secondCache->virtual_base);
if (readoptStatus != B_OK) {
panic("failed to restore cache range: %s",
strerror(readoptStatus));
}
secondCache->ReleaseRefLocked();
addressSpace->ResizeArea(area, oldSize, allocationFlags);
free_etc(areaNewProtections, allocationFlags);
free_etc(secondAreaNewProtections, allocationFlags);
return error;
}
error = cache->Rebase(area->cache_offset, -1);
ASSERT_ALWAYS(error == B_OK);
error = cache->Resize(cache->virtual_base + firstNewSize, resizePriority);
ASSERT_ALWAYS(error == B_OK);
if (resizePriority == -1) {
const off_t areaCommit = compute_area_page_commitment(area) * B_PAGE_SIZE;
if (areaCommit < area->cache->Commitment()) {
secondArea->cache->TakeCommitmentFrom(area->cache,
area->cache->Commitment() - areaCommit);
}
area->cache->Commit(areaCommit, priority);
const off_t secondCommit = compute_area_page_commitment(secondArea) * B_PAGE_SIZE;
secondArea->cache->Commit(secondCommit, priority);
}
} else {
error = map_backing_store(addressSpace, cache, secondCacheOffset,
area->name, secondSize, area->wiring, area->protection,
area->protection_max, REGION_NO_PRIVATE_MAP, 0,
&addressRestrictions, kernel, &secondArea, NULL);
if (error != B_OK) {
addressSpace->ResizeArea(area, oldSize, allocationFlags);
free_etc(areaNewProtections, allocationFlags);
free_etc(secondAreaNewProtections, allocationFlags);
return error;
}
cache->AcquireRefLocked();
}
if (area->page_protections != NULL) {
const size_t areaBytes = area_page_protections_size(area->Size());
memcpy(areaNewProtections, area->page_protections, areaBytes);
uint8* areaOldProtections = area->page_protections;
area->page_protections = areaNewProtections;
const size_t oldBytes = area_page_protections_size(oldSize);
addr_t secondAreaOffset = secondBase - area->Base();
ssize_t secondAreaPagesShifted = secondAreaOffset / B_PAGE_SIZE;
bitmap_shift<uint8>(areaOldProtections, oldBytes * 8, -(secondAreaPagesShifted * 4));
const size_t secondAreaBytes = area_page_protections_size(secondSize);
memcpy(secondAreaNewProtections, areaOldProtections, secondAreaBytes);
secondArea->page_protections = secondAreaNewProtections;
free_etc(areaOldProtections, allocationFlags);
}
if (_secondArea != NULL)
*_secondArea = secondArea;
return B_OK;
}
static status_t
unmap_address_range(VMAddressSpace* addressSpace, addr_t address, addr_t size,
bool kernel)
{
size = PAGE_ALIGN(size);
if (!kernel) {
for (VMAddressSpace::AreaRangeIterator it
= addressSpace->GetAreaRangeIterator(address, size);
VMArea* area = it.Next();) {
if ((area->protection & B_KERNEL_AREA) != 0) {
dprintf("unmap_address_range: team %" B_PRId32 " tried to "
"unmap range of kernel area %" B_PRId32 " (%s)\n",
team_get_current_team_id(), area->id, area->name);
return B_NOT_ALLOWED;
}
}
}
for (VMAddressSpace::AreaRangeIterator it
= addressSpace->GetAreaRangeIterator(address, size);
VMArea* area = it.Next();) {
status_t error = cut_area(addressSpace, area, address, size, NULL,
kernel);
if (error != B_OK)
return error;
}
return B_OK;
}
static status_t
discard_area_range(VMArea* area, addr_t address, addr_t size)
{
addr_t offset;
if (!intersect_area(area, address, size, offset))
return B_OK;
VMCache* cache = vm_area_get_locked_cache(area);
if (cache->areas.First() != area || VMArea::CacheList::GetNext(area) != NULL
|| !cache->consumers.IsEmpty() || cache->type != CACHE_TYPE_RAM) {
return B_OK;
}
VMCacheChainLocker cacheChainLocker(cache);
cacheChainLocker.LockAllSourceCaches();
unmap_pages(area, address, size);
ssize_t commitmentChange = 0;
if (cache->temporary && !cache->CanOvercommit() && area->page_protections != NULL) {
const off_t areaCacheBase = area->Base() - area->cache_offset;
const off_t endAddress = address + size;
for (off_t pageAddress = address; pageAddress < endAddress; pageAddress += B_PAGE_SIZE) {
if (cache->LookupPage(pageAddress - areaCacheBase) == NULL)
continue;
const bool isWritable
= (get_area_page_protection(area, pageAddress) & B_WRITE_AREA) != 0;
if (!isWritable)
commitmentChange -= B_PAGE_SIZE;
}
}
cacheChainLocker.Unlock(cache);
cache->Discard(area->cache_offset + offset, size);
if (commitmentChange != 0)
cache->Commit(cache->Commitment() + commitmentChange, VM_PRIORITY_USER);
cache->ReleaseRefAndUnlock();
return B_OK;
}
static status_t
discard_address_range(VMAddressSpace* addressSpace, addr_t address, addr_t size,
bool kernel)
{
for (VMAddressSpace::AreaRangeIterator it
= addressSpace->GetAreaRangeIterator(address, size);
VMArea* area = it.Next();) {
status_t error = discard_area_range(area, address, size);
if (error != B_OK)
return error;
}
return B_OK;
}
static status_t
map_backing_store(VMAddressSpace* addressSpace, VMCache* cache, off_t offset,
const char* areaName, addr_t size, int wiring, int protection,
int protectionMax, int mapping,
uint32 flags, const virtual_address_restrictions* addressRestrictions,
bool kernel, VMArea** _area, void** _virtualAddress)
{
TRACE(("map_backing_store: aspace %p, cache %p, virtual %p, offset 0x%"
B_PRIx64 ", size %" B_PRIuADDR ", addressSpec %" B_PRIu32 ", wiring %d"
", protection %d, protectionMax %d, area %p, areaName '%s'\n",
addressSpace, cache, addressRestrictions->address, offset, size,
addressRestrictions->address_specification, wiring, protection,
protectionMax, _area, areaName));
cache->AssertLocked();
if (size == 0) {
#if KDEBUG
panic("map_backing_store(): called with size=0 for area '%s'!",
areaName);
#endif
return B_BAD_VALUE;
}
if (offset < 0)
return B_BAD_VALUE;
uint32 allocationFlags = HEAP_DONT_WAIT_FOR_MEMORY
| HEAP_DONT_LOCK_KERNEL_SPACE;
int priority;
if (addressSpace != VMAddressSpace::Kernel()) {
priority = VM_PRIORITY_USER;
} else if ((flags & CREATE_AREA_PRIORITY_VIP) != 0) {
priority = VM_PRIORITY_VIP;
allocationFlags |= HEAP_PRIORITY_VIP;
} else
priority = VM_PRIORITY_SYSTEM;
VMArea* area = addressSpace->CreateArea(areaName, wiring, protection,
allocationFlags);
if (area == NULL)
return B_NO_MEMORY;
if (mapping != REGION_PRIVATE_MAP)
area->protection_max = protectionMax & B_USER_PROTECTION;
status_t status;
VMCache* sourceCache = NULL;
if (mapping == REGION_PRIVATE_MAP) {
VMCache* newCache;
status = VMCacheFactory::CreateAnonymousCache(newCache,
(protection & B_STACK_AREA) != 0
|| (protection & B_OVERCOMMITTING_AREA) != 0, 0,
cache->GuardSize() / B_PAGE_SIZE, true, VM_PRIORITY_USER);
if (status != B_OK)
goto err1;
newCache->Lock();
newCache->temporary = 1;
newCache->virtual_base = offset;
newCache->virtual_end = offset + size;
cache->AddConsumer(newCache);
sourceCache = cache;
cache = newCache;
}
if ((flags & CREATE_AREA_DONT_COMMIT_MEMORY) == 0) {
uint32 commitProtection = B_WRITE_AREA | B_KERNEL_WRITE_AREA;
if (cache->source == NULL)
commitProtection |= B_READ_AREA | B_KERNEL_READ_AREA;
if ((protection & commitProtection) != 0) {
status = cache->SetMinimalCommitment(size, priority);
if (status != B_OK)
goto err2;
}
}
if (addressSpace->IsBeingDeleted()) {
status = B_BAD_TEAM_ID;
goto err2;
}
if (addressRestrictions->address_specification == B_EXACT_ADDRESS
&& (flags & CREATE_AREA_UNMAP_ADDRESS_RANGE) != 0) {
cache->Unlock();
status = unmap_address_range(addressSpace,
(addr_t)addressRestrictions->address, size, kernel);
cache->Lock();
if (status != B_OK)
goto err2;
}
status = addressSpace->InsertArea(area, size, addressRestrictions,
allocationFlags, _virtualAddress);
if (status == B_NO_MEMORY
&& addressRestrictions->address_specification == B_ANY_KERNEL_ADDRESS) {
low_resource(B_KERNEL_RESOURCE_ADDRESS_SPACE, size, B_RELATIVE_TIMEOUT, 0);
}
if (status != B_OK)
goto err2;
area->cache = cache;
area->cache_offset = offset;
cache->InsertAreaLocked(area);
if (mapping == REGION_PRIVATE_MAP)
cache->Unlock();
status = VMAreas::Insert(area);
if (status != B_OK)
goto err3;
addressSpace->Get();
*_area = area;
return B_OK;
err3:
if (mapping != REGION_PRIVATE_MAP)
cache->Unlock();
cache->RemoveArea(area);
area->cache = NULL;
if (mapping != REGION_PRIVATE_MAP)
cache->Lock();
addressSpace->RemoveArea(area, allocationFlags);
err2:
if (mapping == REGION_PRIVATE_MAP) {
if (sourceCache != NULL)
sourceCache->Unlock();
cache->ReleaseRefAndUnlock();
if (sourceCache != NULL)
sourceCache->Lock();
}
err1:
addressSpace->DeleteArea(area, allocationFlags);
return status;
}
template<typename LockerType1, typename LockerType2>
static inline bool
wait_if_area_is_wired(VMArea* area, LockerType1* locker1, LockerType2* locker2)
{
area->cache->AssertLocked();
VMAreaUnwiredWaiter waiter;
if (!area->AddWaiterIfWired(&waiter))
return false;
if (locker1 != NULL)
locker1->Unlock();
if (locker2 != NULL)
locker2->Unlock();
waiter.waitEntry.Wait();
return true;
}
template<typename LockerType1, typename LockerType2>
static inline bool
wait_if_area_range_is_wired(VMArea* area, addr_t base, size_t size,
LockerType1* locker1, LockerType2* locker2)
{
area->cache->AssertLocked();
VMAreaUnwiredWaiter waiter;
if (!area->AddWaiterIfWired(&waiter, base, size))
return false;
if (locker1 != NULL)
locker1->Unlock();
if (locker2 != NULL)
locker2->Unlock();
waiter.waitEntry.Wait();
return true;
}
template<typename LockerType>
static inline bool
wait_if_address_range_is_wired(VMAddressSpace* addressSpace, addr_t base,
size_t size, LockerType* locker)
{
VMAddressSpace::AreaRangeIterator it = addressSpace->GetAreaRangeIterator(base, size);
while (VMArea* area = it.Next()) {
AreaCacheLocker cacheLocker(area);
if (wait_if_area_range_is_wired(area, base, size, locker, &cacheLocker))
return true;
}
return false;
}
status_t
vm_block_address_range(const char* name, void* address, addr_t size)
{
AddressSpaceWriteLocker locker;
status_t status = locker.SetTo(VMAddressSpace::KernelID());
if (status != B_OK)
return status;
VMAddressSpace* addressSpace = locker.AddressSpace();
VMCache* cache;
status = VMCacheFactory::CreateNullCache(VM_PRIORITY_SYSTEM, cache);
if (status != B_OK)
return status;
cache->temporary = 1;
cache->virtual_end = size;
cache->Lock();
VMArea* area;
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = address;
addressRestrictions.address_specification = B_EXACT_ADDRESS;
status = map_backing_store(addressSpace, cache, 0, name, size,
B_NO_LOCK, 0, REGION_NO_PRIVATE_MAP, 0, CREATE_AREA_DONT_COMMIT_MEMORY,
&addressRestrictions, true, &area, NULL);
if (status != B_OK) {
cache->ReleaseRefAndUnlock();
return status;
}
cache->Unlock();
area->cache_type = CACHE_TYPE_NULL;
return area->id;
}
status_t
vm_unreserve_address_range(team_id team, void* address, addr_t size)
{
AddressSpaceWriteLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
VMAddressSpace* addressSpace = locker.AddressSpace();
return addressSpace->UnreserveAddressRange((addr_t)address, size,
addressSpace == VMAddressSpace::Kernel()
? HEAP_DONT_WAIT_FOR_MEMORY | HEAP_DONT_LOCK_KERNEL_SPACE : 0);
}
status_t
vm_reserve_address_range(team_id team, void** _address, uint32 addressSpec,
addr_t size, uint32 flags)
{
if (size == 0)
return B_BAD_VALUE;
AddressSpaceWriteLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = *_address;
addressRestrictions.address_specification = addressSpec;
VMAddressSpace* addressSpace = locker.AddressSpace();
return addressSpace->ReserveAddressRange(size, &addressRestrictions, flags,
addressSpace == VMAddressSpace::Kernel()
? HEAP_DONT_WAIT_FOR_MEMORY | HEAP_DONT_LOCK_KERNEL_SPACE : 0,
_address);
}
area_id
vm_create_anonymous_area(team_id team, const char *name, addr_t size,
uint32 wiring, uint32 protection, uint32 flags, addr_t guardSize,
const virtual_address_restrictions* virtualAddressRestrictions,
const physical_address_restrictions* physicalAddressRestrictions,
bool kernel, void** _address)
{
VMArea* area;
VMCache* cache;
vm_page* page = NULL;
bool isStack = (protection & B_STACK_AREA) != 0;
page_num_t guardPages;
bool canOvercommit = false;
uint32 pageAllocFlags = (flags & CREATE_AREA_DONT_CLEAR) == 0
? VM_PAGE_ALLOC_CLEAR : 0;
TRACE(("create_anonymous_area [%" B_PRId32 "] %s: size 0x%" B_PRIxADDR "\n",
team, name, size));
size = PAGE_ALIGN(size);
guardSize = PAGE_ALIGN(guardSize);
guardPages = guardSize / B_PAGE_SIZE;
if (size == 0 || size < guardSize)
return B_BAD_VALUE;
status_t status = check_protection(team, &protection);
if (status != B_OK)
return status;
if (isStack || (protection & B_OVERCOMMITTING_AREA) != 0)
canOvercommit = true;
#ifdef DEBUG_KERNEL_STACKS
if ((protection & B_KERNEL_STACK_AREA) != 0)
isStack = true;
#endif
switch (virtualAddressRestrictions->address_specification) {
case B_ANY_ADDRESS:
case B_EXACT_ADDRESS:
case B_BASE_ADDRESS:
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
case B_RANDOMIZED_ANY_ADDRESS:
case B_RANDOMIZED_BASE_ADDRESS:
break;
default:
return B_BAD_VALUE;
}
if (physicalAddressRestrictions->low_address != 0
|| physicalAddressRestrictions->high_address != 0) {
wiring = B_CONTIGUOUS;
}
physical_address_restrictions stackPhysicalRestrictions;
bool doReserveMemory = false;
addr_t reservedMemory = 0;
switch (wiring) {
case B_NO_LOCK:
break;
case B_FULL_LOCK:
case B_LAZY_LOCK:
case B_CONTIGUOUS:
doReserveMemory = true;
break;
case B_LOMEM:
stackPhysicalRestrictions = *physicalAddressRestrictions;
stackPhysicalRestrictions.high_address = 16 * 1024 * 1024;
physicalAddressRestrictions = &stackPhysicalRestrictions;
wiring = B_CONTIGUOUS;
doReserveMemory = true;
break;
case B_32_BIT_FULL_LOCK:
if (B_HAIKU_PHYSICAL_BITS <= 32
|| (uint64)vm_page_max_address() < (uint64)1 << 32) {
wiring = B_FULL_LOCK;
doReserveMemory = true;
break;
}
case B_32_BIT_CONTIGUOUS:
#if B_HAIKU_PHYSICAL_BITS > 32
if (vm_page_max_address() >= (phys_addr_t)1 << 32) {
stackPhysicalRestrictions = *physicalAddressRestrictions;
stackPhysicalRestrictions.high_address
= (phys_addr_t)1 << 32;
physicalAddressRestrictions = &stackPhysicalRestrictions;
}
#endif
wiring = B_CONTIGUOUS;
doReserveMemory = true;
break;
case B_ALREADY_WIRED:
ASSERT(gKernelStartup);
reservedMemory = size;
break;
default:
return B_BAD_VALUE;
}
if (wiring == B_CONTIGUOUS && size == B_PAGE_SIZE
&& physicalAddressRestrictions->low_address == 0
&& physicalAddressRestrictions->high_address == 0) {
wiring = B_FULL_LOCK;
}
addr_t reservedMapPages = 0;
if (wiring == B_FULL_LOCK || wiring == B_CONTIGUOUS) {
AddressSpaceWriteLocker locker;
status_t status = locker.SetTo(team);
if (status != B_OK)
return status;
VMTranslationMap* map = locker.AddressSpace()->TranslationMap();
reservedMapPages = map->MaxPagesNeededToMap(0, size - 1);
}
int priority;
if (team != VMAddressSpace::KernelID())
priority = VM_PRIORITY_USER;
else if ((flags & CREATE_AREA_PRIORITY_VIP) != 0)
priority = VM_PRIORITY_VIP;
else
priority = VM_PRIORITY_SYSTEM;
if (doReserveMemory) {
bigtime_t timeout = (flags & CREATE_AREA_DONT_WAIT) != 0 ? 0 : 1000000;
if (vm_try_reserve_memory(size, priority, timeout) != B_OK)
return B_NO_MEMORY;
reservedMemory = size;
}
AddressSpaceWriteLocker locker;
VMAddressSpace* addressSpace;
page_num_t reservedPages = reservedMapPages;
if (wiring == B_FULL_LOCK)
reservedPages += size / B_PAGE_SIZE;
vm_page_reservation reservation;
if (reservedPages > 0) {
if ((flags & CREATE_AREA_DONT_WAIT) != 0) {
if (!vm_page_try_reserve_pages(&reservation, reservedPages,
priority)) {
reservedPages = 0;
status = B_WOULD_BLOCK;
goto err0;
}
} else
vm_page_reserve_pages(&reservation, reservedPages, priority);
}
if (wiring == B_CONTIGUOUS) {
page = vm_page_allocate_page_run(PAGE_STATE_WIRED | pageAllocFlags,
size / B_PAGE_SIZE, physicalAddressRestrictions, priority);
if (page == NULL) {
status = B_NO_MEMORY;
goto err0;
}
}
do {
status = locker.SetTo(team);
if (status != B_OK)
goto err1;
addressSpace = locker.AddressSpace();
} while (virtualAddressRestrictions->address_specification
== B_EXACT_ADDRESS
&& (flags & CREATE_AREA_UNMAP_ADDRESS_RANGE) != 0
&& wait_if_address_range_is_wired(addressSpace,
(addr_t)virtualAddressRestrictions->address, size, &locker));
status = VMCacheFactory::CreateAnonymousCache(cache, canOvercommit,
isStack ? (min_c(2, size / B_PAGE_SIZE - guardPages)) : 0, guardPages,
wiring == B_NO_LOCK, priority);
if (status != B_OK)
goto err1;
cache->temporary = 1;
cache->virtual_end = size;
if (reservedMemory != 0) {
VMAnonymousNoSwapCache* noSwapCache = dynamic_cast<VMAnonymousNoSwapCache*>(cache);
noSwapCache->committed_size = reservedMemory;
reservedMemory = 0;
}
cache->Lock();
status = map_backing_store(addressSpace, cache, 0, name, size, wiring,
protection, 0, REGION_NO_PRIVATE_MAP, flags,
virtualAddressRestrictions, kernel, &area, _address);
if (status != B_OK) {
cache->ReleaseRefAndUnlock();
goto err1;
}
locker.DegradeToReadLock();
switch (wiring) {
case B_NO_LOCK:
case B_LAZY_LOCK:
break;
case B_FULL_LOCK:
{
off_t offset = 0;
for (addr_t address = area->Base();
address < area->Base() + (area->Size() - 1);
address += B_PAGE_SIZE, offset += B_PAGE_SIZE) {
#ifdef DEBUG_KERNEL_STACKS
# ifdef STACK_GROWS_DOWNWARDS
if (isStack && address < area->Base()
+ KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE)
# else
if (isStack && address >= area->Base() + area->Size()
- KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE)
# endif
continue;
#endif
vm_page* page = vm_page_allocate_page(&reservation,
PAGE_STATE_WIRED | pageAllocFlags);
cache->InsertPage(page, offset);
map_page(area, page, address, protection, &reservation);
DEBUG_PAGE_ACCESS_END(page);
}
break;
}
case B_ALREADY_WIRED:
{
VMTranslationMap* map = addressSpace->TranslationMap();
off_t offset = 0;
if (!gKernelStartup)
panic("ALREADY_WIRED flag used outside kernel startup\n");
map->Lock();
for (addr_t virtualAddress = area->Base();
virtualAddress < area->Base() + (area->Size() - 1);
virtualAddress += B_PAGE_SIZE, offset += B_PAGE_SIZE) {
phys_addr_t physicalAddress;
uint32 flags;
status = map->Query(virtualAddress, &physicalAddress, &flags);
if (status < B_OK) {
panic("looking up mapping failed for va 0x%lx\n",
virtualAddress);
}
page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL) {
panic("looking up page failed for pa %#" B_PRIxPHYSADDR
"\n", physicalAddress);
}
DEBUG_PAGE_ACCESS_START(page);
cache->InsertPage(page, offset);
increment_page_wired_count(page);
vm_page_set_state(page, PAGE_STATE_WIRED);
page->busy = false;
DEBUG_PAGE_ACCESS_END(page);
}
map->Unlock();
break;
}
case B_CONTIGUOUS:
{
VMTranslationMap* map = addressSpace->TranslationMap();
phys_addr_t physicalAddress
= (phys_addr_t)page->physical_page_number * B_PAGE_SIZE;
addr_t virtualAddress = area->Base();
off_t offset = 0;
map->Lock();
for (virtualAddress = area->Base(); virtualAddress < area->Base()
+ (area->Size() - 1); virtualAddress += B_PAGE_SIZE,
offset += B_PAGE_SIZE, physicalAddress += B_PAGE_SIZE) {
page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL)
panic("couldn't lookup physical page just allocated\n");
status = map->Map(virtualAddress, physicalAddress, protection,
area->MemoryType(), &reservation);
if (status < B_OK)
panic("couldn't map physical page in page run\n");
cache->InsertPage(page, offset);
increment_page_wired_count(page);
DEBUG_PAGE_ACCESS_END(page);
}
map->Unlock();
break;
}
default:
break;
}
cache->Unlock();
if (reservedPages > 0)
vm_page_unreserve_pages(&reservation);
TRACE(("vm_create_anonymous_area: done\n"));
area->cache_type = CACHE_TYPE_RAM;
return area->id;
err1:
if (wiring == B_CONTIGUOUS) {
phys_addr_t pageNumber = page->physical_page_number;
int32 i;
for (i = size / B_PAGE_SIZE; i-- > 0; pageNumber++) {
page = vm_lookup_page(pageNumber);
if (page == NULL)
panic("couldn't lookup physical page just allocated\n");
vm_page_free(NULL, page);
}
}
err0:
if (reservedPages > 0)
vm_page_unreserve_pages(&reservation);
if (reservedMemory > 0)
vm_unreserve_memory(reservedMemory);
ASSERT(wiring != B_ALREADY_WIRED);
return status;
}
area_id
vm_map_physical_memory(team_id team, const char* name, void** _address,
uint32 addressSpec, addr_t size, uint32 protection,
phys_addr_t physicalAddress, bool alreadyWired)
{
VMArea* area;
VMCache* cache;
addr_t mapOffset;
TRACE(("vm_map_physical_memory(aspace = %" B_PRId32 ", \"%s\", virtual = %p"
", spec = %" B_PRIu32 ", size = %" B_PRIxADDR ", protection = %"
B_PRIu32 ", phys = %#" B_PRIxPHYSADDR ")\n", team, name, *_address,
addressSpec, size, protection, physicalAddress));
status_t status = check_protection(team, &protection);
if (status != B_OK)
return status;
AddressSpaceWriteLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
mapOffset = physicalAddress % B_PAGE_SIZE;
size += mapOffset;
physicalAddress -= mapOffset;
size = PAGE_ALIGN(size);
status = VMCacheFactory::CreateDeviceCache(cache, physicalAddress);
if (status != B_OK)
return status;
cache->virtual_end = size;
cache->Lock();
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = *_address;
addressRestrictions.address_specification = addressSpec & ~B_MEMORY_TYPE_MASK;
status = map_backing_store(locker.AddressSpace(), cache, 0, name, size,
B_FULL_LOCK, protection, 0, REGION_NO_PRIVATE_MAP, CREATE_AREA_DONT_COMMIT_MEMORY,
&addressRestrictions, true, &area, _address);
if (status < B_OK)
cache->ReleaseRefLocked();
cache->Unlock();
if (status == B_OK) {
uint32 memoryType = addressSpec & B_MEMORY_TYPE_MASK;
const bool weak = (memoryType == 0);
if (weak)
memoryType = B_UNCACHED_MEMORY;
status = arch_vm_set_memory_type(area, physicalAddress, memoryType,
weak ? &memoryType : NULL);
area->SetMemoryType(memoryType);
if (status != B_OK)
delete_area(locker.AddressSpace(), area, false);
}
if (status != B_OK)
return status;
VMTranslationMap* map = locker.AddressSpace()->TranslationMap();
if (alreadyWired) {
map->Lock();
map->ProtectArea(area, area->protection);
map->Unlock();
} else {
size_t reservePages = map->MaxPagesNeededToMap(area->Base(),
area->Base() + (size - 1));
vm_page_reservation reservation;
vm_page_reserve_pages(&reservation, reservePages,
team == VMAddressSpace::KernelID()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
map->Lock();
for (addr_t offset = 0; offset < size; offset += B_PAGE_SIZE) {
map->Map(area->Base() + offset, physicalAddress + offset,
protection, area->MemoryType(), &reservation);
}
map->Unlock();
vm_page_unreserve_pages(&reservation);
}
*_address = (void*)((addr_t)*_address + mapOffset);
area->cache_type = CACHE_TYPE_DEVICE;
return area->id;
}
area_id
vm_map_physical_memory_vecs(team_id team, const char* name, void** _address,
uint32 addressSpec, addr_t* _size, uint32 protection,
struct generic_io_vec* vecs, uint32 vecCount)
{
TRACE(("vm_map_physical_memory_vecs(team = %" B_PRId32 ", \"%s\", virtual "
"= %p, spec = %" B_PRIu32 ", _size = %p, protection = %" B_PRIu32 ", "
"vecs = %p, vecCount = %" B_PRIu32 ")\n", team, name, *_address,
addressSpec, _size, protection, vecs, vecCount));
status_t status = check_protection(team, &protection);
if (status != B_OK)
return status;
if ((addressSpec & B_MEMORY_TYPE_MASK) != 0)
return B_NOT_SUPPORTED;
AddressSpaceWriteLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
if (vecCount == 0)
return B_BAD_VALUE;
addr_t size = 0;
for (uint32 i = 0; i < vecCount; i++) {
if (vecs[i].base % B_PAGE_SIZE != 0
|| vecs[i].length % B_PAGE_SIZE != 0) {
return B_BAD_VALUE;
}
size += vecs[i].length;
}
VMCache* cache;
status_t result = VMCacheFactory::CreateDeviceCache(cache, vecs[0].base);
if (result != B_OK)
return result;
cache->virtual_end = size;
cache->Lock();
VMArea* area;
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = *_address;
addressRestrictions.address_specification = addressSpec & ~B_MEMORY_TYPE_MASK;
result = map_backing_store(locker.AddressSpace(), cache, 0, name, size,
B_FULL_LOCK, protection, 0, REGION_NO_PRIVATE_MAP, CREATE_AREA_DONT_COMMIT_MEMORY,
&addressRestrictions, true, &area, _address);
if (result != B_OK)
cache->ReleaseRefLocked();
cache->Unlock();
if (result != B_OK)
return result;
VMTranslationMap* map = locker.AddressSpace()->TranslationMap();
size_t reservePages = map->MaxPagesNeededToMap(area->Base(),
area->Base() + (size - 1));
vm_page_reservation reservation;
vm_page_reserve_pages(&reservation, reservePages,
team == VMAddressSpace::KernelID()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
map->Lock();
uint32 vecIndex = 0;
size_t vecOffset = 0;
for (addr_t offset = 0; offset < size; offset += B_PAGE_SIZE) {
while (vecOffset >= vecs[vecIndex].length && vecIndex < vecCount) {
vecOffset = 0;
vecIndex++;
}
if (vecIndex >= vecCount)
break;
map->Map(area->Base() + offset, vecs[vecIndex].base + vecOffset,
protection, area->MemoryType(), &reservation);
vecOffset += B_PAGE_SIZE;
}
map->Unlock();
vm_page_unreserve_pages(&reservation);
if (_size != NULL)
*_size = size;
area->cache_type = CACHE_TYPE_DEVICE;
return area->id;
}
area_id
vm_create_null_area(team_id team, const char* name, void** address,
uint32 addressSpec, addr_t size, uint32 flags)
{
size = PAGE_ALIGN(size);
AddressSpaceWriteLocker locker;
do {
if (locker.SetTo(team) != B_OK)
return B_BAD_TEAM_ID;
} while (addressSpec == B_EXACT_ADDRESS
&& (flags & CREATE_AREA_UNMAP_ADDRESS_RANGE) != 0
&& wait_if_address_range_is_wired(locker.AddressSpace(),
(addr_t)*address, size, &locker));
int priority = (flags & CREATE_AREA_PRIORITY_VIP) != 0
? VM_PRIORITY_VIP : VM_PRIORITY_SYSTEM;
VMCache* cache;
status_t status = VMCacheFactory::CreateNullCache(priority, cache);
if (status != B_OK)
return status;
cache->temporary = 1;
cache->virtual_end = size;
cache->Lock();
VMArea* area;
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = *address;
addressRestrictions.address_specification = addressSpec;
status = map_backing_store(locker.AddressSpace(), cache, 0, name, size,
B_LAZY_LOCK, B_KERNEL_READ_AREA, B_KERNEL_READ_AREA,
REGION_NO_PRIVATE_MAP, flags | CREATE_AREA_DONT_COMMIT_MEMORY,
&addressRestrictions, true, &area, address);
if (status < B_OK) {
cache->ReleaseRefAndUnlock();
return status;
}
cache->Unlock();
area->cache_type = CACHE_TYPE_NULL;
return area->id;
}
status_t
vm_create_vnode_cache(struct vnode* vnode, struct VMCache** cache)
{
return VMCacheFactory::CreateVnodeCache(*cache, vnode);
}
static void
pre_map_area_pages(VMArea* area, VMCache* cache,
vm_page_reservation* reservation, int32 maxCount)
{
addr_t baseAddress = area->Base();
addr_t cacheOffset = area->cache_offset;
page_num_t firstPage = cacheOffset / B_PAGE_SIZE;
page_num_t endPage = firstPage + area->Size() / B_PAGE_SIZE;
VMCachePagesTree::Iterator it = cache->pages.GetIterator(firstPage, true, true);
vm_page* page;
while ((page = it.Next()) != NULL && maxCount > 0) {
if (page->cache_offset >= endPage)
break;
if (page->busy || (page->usage_count == 0 && !page->accessed))
continue;
DEBUG_PAGE_ACCESS_START(page);
map_page(area, page,
baseAddress + (page->cache_offset * B_PAGE_SIZE - cacheOffset),
B_READ_AREA | B_KERNEL_READ_AREA, reservation);
maxCount--;
DEBUG_PAGE_ACCESS_END(page);
}
}
static area_id
_vm_map_file(team_id team, const char* name, void** _address,
uint32 addressSpec, size_t size, uint32 protection, uint32 mapping,
bool unmapAddressRange, int fd, off_t offset, bool kernel)
{
TRACE(("_vm_map_file(fd = %d, offset = %" B_PRIdOFF ", size = %lu, mapping "
"%" B_PRIu32 ")\n", fd, offset, size, mapping));
status_t status = check_protection(team, &protection);
if (status != B_OK)
return status;
if ((offset % B_PAGE_SIZE) != 0)
return B_BAD_VALUE;
size = PAGE_ALIGN(size);
if (mapping == REGION_NO_PRIVATE_MAP)
protection |= B_SHARED_AREA;
if (addressSpec != B_EXACT_ADDRESS)
unmapAddressRange = false;
uint32 mappingFlags = 0;
if (unmapAddressRange)
mappingFlags |= CREATE_AREA_UNMAP_ADDRESS_RANGE;
if (fd < 0) {
virtual_address_restrictions virtualRestrictions = {};
virtualRestrictions.address = *_address;
virtualRestrictions.address_specification = addressSpec;
physical_address_restrictions physicalRestrictions = {};
return vm_create_anonymous_area(team, name, size, B_NO_LOCK, protection,
mappingFlags, 0, &virtualRestrictions, &physicalRestrictions, kernel,
_address);
}
file_descriptor* descriptor = get_fd(get_current_io_context(kernel), fd);
if (descriptor == NULL)
return EBADF;
int32 openMode = descriptor->open_mode;
put_fd(descriptor);
if ((openMode & O_ACCMODE) == O_WRONLY
|| (mapping == REGION_NO_PRIVATE_MAP
&& (protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0
&& (openMode & O_ACCMODE) == O_RDONLY)) {
return EACCES;
}
uint32 protectionMax = 0;
if (mapping == REGION_NO_PRIVATE_MAP) {
if ((openMode & O_ACCMODE) == O_RDWR)
protectionMax = protection | B_USER_PROTECTION;
else
protectionMax = protection | (B_USER_PROTECTION & ~B_WRITE_AREA);
}
struct vnode* vnode = NULL;
status = vfs_get_vnode_from_fd(fd, kernel, &vnode);
if (status < B_OK)
return status;
VnodePutter vnodePutter(vnode);
page_num_t reservedPreMapPages = 0;
vm_page_reservation reservation;
if ((protection & B_READ_AREA) != 0) {
AddressSpaceWriteLocker locker;
status = locker.SetTo(team);
if (status != B_OK)
return status;
VMTranslationMap* map = locker.AddressSpace()->TranslationMap();
reservedPreMapPages = map->MaxPagesNeededToMap(0, size - 1);
locker.Unlock();
vm_page_reserve_pages(&reservation, reservedPreMapPages,
team == VMAddressSpace::KernelID()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
}
struct PageUnreserver {
PageUnreserver(vm_page_reservation* reservation)
:
fReservation(reservation)
{
}
~PageUnreserver()
{
if (fReservation != NULL)
vm_page_unreserve_pages(fReservation);
}
vm_page_reservation* fReservation;
} pageUnreserver(reservedPreMapPages > 0 ? &reservation : NULL);
AddressSpaceWriteLocker locker;
do {
if (locker.SetTo(team) != B_OK)
return B_BAD_TEAM_ID;
} while (unmapAddressRange
&& wait_if_address_range_is_wired(locker.AddressSpace(),
(addr_t)*_address, size, &locker));
VMCache* cache;
status = vfs_get_vnode_cache(vnode, &cache, false);
if (status < B_OK)
return status;
cache->Lock();
if (mapping != REGION_PRIVATE_MAP && (cache->virtual_base > offset
|| PAGE_ALIGN(cache->virtual_end) < (off_t)(offset + size))) {
cache->ReleaseRefAndUnlock();
return B_BAD_VALUE;
}
VMArea* area;
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = *_address;
addressRestrictions.address_specification = addressSpec;
status = map_backing_store(locker.AddressSpace(), cache, offset, name, size,
0, protection, protectionMax, mapping, mappingFlags,
&addressRestrictions, kernel, &area, _address);
if (status != B_OK || mapping == REGION_PRIVATE_MAP) {
cache->ReleaseRefLocked();
}
if (status == B_OK && (protection & B_READ_AREA) != 0 && cache->page_count > 0) {
pre_map_area_pages(area, cache, &reservation,
(cache->FaultCount() / cache->page_count)
* ((1 * 1024 * 1024) / B_PAGE_SIZE));
}
cache->Unlock();
if (status == B_OK) {
const size_t prefetch = min_c(size, 10LL * 1024 * 1024);
if (cache->page_count < (prefetch / B_PAGE_SIZE))
cache_prefetch_vnode(vnode, offset, prefetch);
}
if (status != B_OK)
return status;
area->cache_type = CACHE_TYPE_VNODE;
return area->id;
}
area_id
vm_map_file(team_id aid, const char* name, void** address, uint32 addressSpec,
addr_t size, uint32 protection, uint32 mapping, bool unmapAddressRange,
int fd, off_t offset)
{
return _vm_map_file(aid, name, address, addressSpec, size, protection,
mapping, unmapAddressRange, fd, offset, true);
}
VMCache*
vm_area_get_locked_cache(VMArea* area)
{
rw_lock_read_lock(&sAreaCacheLock);
while (true) {
VMCache* cache = area->cache;
if (!cache->SwitchFromReadLock(&sAreaCacheLock)) {
rw_lock_read_lock(&sAreaCacheLock);
continue;
}
rw_lock_read_lock(&sAreaCacheLock);
if (cache == area->cache) {
cache->AcquireRefLocked();
rw_lock_read_unlock(&sAreaCacheLock);
return cache;
}
cache->Unlock();
}
}
void
vm_area_put_locked_cache(VMCache* cache)
{
cache->ReleaseRefAndUnlock();
}
area_id
vm_clone_area(team_id team, const char* name, void** address,
uint32 addressSpec, uint32 protection, uint32 mapping, area_id sourceID,
bool kernel)
{
status_t status = check_protection(team, &protection);
if (status != B_OK)
return status;
{
AddressSpaceWriteLocker locker;
VMArea* sourceArea;
status = locker.SetFromArea(sourceID, sourceArea);
if (status != B_OK)
return status;
if (!kernel && (sourceArea->protection & B_KERNEL_AREA) != 0)
return B_NOT_ALLOWED;
sourceArea->protection |= B_SHARED_AREA;
protection |= B_SHARED_AREA;
}
MultiAddressSpaceLocker locker;
VMAddressSpace* sourceAddressSpace;
status = locker.AddArea(sourceID, false, &sourceAddressSpace);
if (status != B_OK)
return status;
VMAddressSpace* targetAddressSpace;
status = locker.AddTeam(team, true, &targetAddressSpace);
if (status != B_OK)
return status;
status = locker.Lock();
if (status != B_OK)
return status;
VMArea* sourceArea = lookup_area(sourceAddressSpace, sourceID);
if (sourceArea == NULL)
return B_BAD_VALUE;
if (!kernel && (sourceArea->protection & B_KERNEL_AREA) != 0)
return B_NOT_ALLOWED;
AreaCacheLocker cacheLocker(sourceArea);
VMCache* cache = cacheLocker.Get();
int protectionMax = sourceArea->protection_max;
if (!kernel && sourceAddressSpace != targetAddressSpace) {
if ((sourceArea->protection & B_CLONEABLE_AREA) == 0) {
#if KDEBUG
Team* team = thread_get_current_thread()->team;
dprintf("team \"%s\" (%" B_PRId32 ") attempted to clone area \"%s\" (%"
B_PRId32 ")!\n", team->Name(), team->id, sourceArea->name, sourceID);
#endif
return B_NOT_ALLOWED;
}
if (protectionMax == 0)
protectionMax = B_USER_PROTECTION;
if ((sourceArea->protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) == 0)
protectionMax &= ~B_WRITE_AREA;
if (((protection & B_USER_PROTECTION) & ~protectionMax) != 0) {
#if KDEBUG
Team* team = thread_get_current_thread()->team;
dprintf("team \"%s\" (%" B_PRId32 ") attempted to clone area \"%s\" (%"
B_PRId32 ") with extra permissions (0x%x)!\n", team->Name(), team->id,
sourceArea->name, sourceID, protection);
#endif
return B_NOT_ALLOWED;
}
}
if (sourceArea->cache_type == CACHE_TYPE_NULL)
return B_NOT_ALLOWED;
uint32 mappingFlags = 0;
if (mapping != REGION_PRIVATE_MAP)
mappingFlags |= CREATE_AREA_DONT_COMMIT_MEMORY;
virtual_address_restrictions addressRestrictions = {};
VMArea* newArea;
addressRestrictions.address = *address;
addressRestrictions.address_specification = addressSpec;
status = map_backing_store(targetAddressSpace, cache,
sourceArea->cache_offset, name, sourceArea->Size(),
sourceArea->wiring, protection, protectionMax,
mapping, mappingFlags, &addressRestrictions,
kernel, &newArea, address);
if (status < B_OK)
return status;
if (mapping != REGION_PRIVATE_MAP) {
cache->AcquireRefLocked();
}
if (newArea->wiring == B_FULL_LOCK) {
if (sourceArea->cache_type == CACHE_TYPE_DEVICE) {
uint32 memoryType = sourceArea->MemoryType();
newArea->SetMemoryType(sourceArea->MemoryType());
VMTranslationMap* map
= sourceArea->address_space->TranslationMap();
map->Lock();
phys_addr_t physicalAddress;
uint32 oldProtection;
map->Query(sourceArea->Base(), &physicalAddress, &oldProtection);
map->Unlock();
map = targetAddressSpace->TranslationMap();
size_t reservePages = map->MaxPagesNeededToMap(newArea->Base(),
newArea->Base() + (newArea->Size() - 1));
vm_page_reservation reservation;
vm_page_reserve_pages(&reservation, reservePages,
targetAddressSpace == VMAddressSpace::Kernel()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
map->Lock();
for (addr_t offset = 0; offset < newArea->Size();
offset += B_PAGE_SIZE) {
map->Map(newArea->Base() + offset, physicalAddress + offset,
protection, newArea->MemoryType(), &reservation);
}
map->Unlock();
vm_page_unreserve_pages(&reservation);
if (memoryType != 0) {
status = arch_vm_set_memory_type(newArea, physicalAddress,
memoryType, NULL);
ASSERT_ALWAYS(status == B_OK);
}
} else {
VMTranslationMap* map = targetAddressSpace->TranslationMap();
size_t reservePages = map->MaxPagesNeededToMap(
newArea->Base(), newArea->Base() + (newArea->Size() - 1));
vm_page_reservation reservation;
vm_page_reserve_pages(&reservation, reservePages,
targetAddressSpace == VMAddressSpace::Kernel()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
for (VMCachePagesTree::Iterator it = cache->pages.GetIterator();
vm_page* page = it.Next();) {
if (!page->busy) {
DEBUG_PAGE_ACCESS_START(page);
map_page(newArea, page,
newArea->Base() + ((page->cache_offset << PAGE_SHIFT)
- newArea->cache_offset),
protection, &reservation);
DEBUG_PAGE_ACCESS_END(page);
}
}
vm_page_unreserve_pages(&reservation);
}
}
newArea->cache_type = sourceArea->cache_type;
return newArea->id;
}
status_t
vm_change_cache_of_clones(area_id areaId, struct VMCache* toCache)
{
AddressSpaceReadLocker addressSpaceLocker;
VMArea* fromArea;
addressSpaceLocker.SetFromArea(areaId, fromArea);
VMCache* cache = vm_area_get_locked_cache(fromArea);
addressSpaceLocker.Unlock();
VMCacheChainLocker cacheChainLocker(cache);
cacheChainLocker.LockAllSourceCaches();
VMArea* area = NULL;
int32 releaseStoreRefs = 0;
while (true) {
if (area == NULL)
area = cache->areas.First();
else
area = cache->areas.GetNext(area);
if (area == NULL)
break;
if (area == fromArea)
continue;
ASSERT(!area->IsWired());
unmap_pages(area, area->Base(), area->Size());
rw_lock_write_lock(&sAreaCacheLock);
cache->areas.Remove(area);
area->cache = toCache;
area->cache_type = cache->type;
toCache->InsertAreaLocked(area);
toCache->AcquireRefLocked();
rw_lock_write_unlock(&sAreaCacheLock);
cache->ReleaseRefLocked();
releaseStoreRefs++;
area = NULL;
}
cacheChainLocker.Unlock(cache);
cache->Unlock();
while (releaseStoreRefs > 0) {
cache->ReleaseStoreRef();
releaseStoreRefs--;
}
cache->ReleaseRef();
return B_OK;
}
status_t
vm_change_clones_to_null_areas(area_id areaId)
{
VMCache* cache;
status_t status = VMCacheFactory::CreateNullCache(VM_PRIORITY_SYSTEM, cache);
if (status != B_OK)
return status;
cache->Lock();
cache->temporary = 1;
cache->virtual_end = SSIZE_MAX;
status = vm_change_cache_of_clones(areaId, cache);
cache->ReleaseRefAndUnlock();
return status;
}
static void
delete_area(VMAddressSpace* addressSpace, VMArea* area,
bool deletingAddressSpace, bool alreadyRemoved)
{
ASSERT(!area->IsWired());
if (area->id >= 0 && !alreadyRemoved)
VMAreas::Remove(area);
{
VMCache* topCache = vm_area_get_locked_cache(area);
VMCacheChainLocker cacheChainLocker(topCache);
cacheChainLocker.LockAllSourceCaches();
bool ignoreTopCachePageFlags
= topCache->temporary && topCache->RefCount() == 2;
area->address_space->TranslationMap()->UnmapArea(area,
deletingAddressSpace, ignoreTopCachePageFlags);
}
if (!area->cache->temporary)
area->cache->WriteModified();
uint32 allocationFlags = addressSpace == VMAddressSpace::Kernel()
? HEAP_DONT_WAIT_FOR_MEMORY | HEAP_DONT_LOCK_KERNEL_SPACE : 0;
arch_vm_unset_memory_type(area);
addressSpace->RemoveArea(area, allocationFlags);
addressSpace->Put();
area->cache->RemoveArea(area);
area->cache->ReleaseRef();
addressSpace->DeleteArea(area, allocationFlags);
}
status_t
vm_delete_area(team_id team, area_id id, bool kernel)
{
TRACE(("vm_delete_area(team = 0x%" B_PRIx32 ", area = 0x%" B_PRIx32 ")\n",
team, id));
AddressSpaceWriteLocker locker;
VMArea* area;
AreaCacheLocker cacheLocker;
do {
status_t status = locker.SetFromArea(team, id, area);
if (status != B_OK)
return status;
cacheLocker.SetTo(area);
} while (wait_if_area_is_wired(area, &locker, &cacheLocker));
cacheLocker.Unlock();
if (!kernel && (area->protection & B_KERNEL_AREA) != 0)
return B_NOT_ALLOWED;
delete_area(locker.AddressSpace(), area, false);
return B_OK;
}
static status_t
vm_copy_on_write_area(VMCache* lowerCache,
vm_page_reservation* wiredPagesReservation)
{
TRACE(("vm_copy_on_write_area(cache = %p)\n", lowerCache));
VMCache* upperCache;
status_t status = VMCacheFactory::CreateAnonymousCache(upperCache,
lowerCache->CanOvercommit(), 0,
lowerCache->GuardSize() / B_PAGE_SIZE,
dynamic_cast<VMAnonymousNoSwapCache*>(lowerCache) == NULL,
VM_PRIORITY_USER);
if (status != B_OK)
return status;
upperCache->Lock();
upperCache->temporary = 1;
upperCache->virtual_base = lowerCache->virtual_base;
upperCache->virtual_end = lowerCache->virtual_end;
const off_t lowerOldCommitment = lowerCache->Commitment(),
lowerNewCommitment = (lowerCache->page_count * B_PAGE_SIZE);
if (lowerNewCommitment < lowerOldCommitment) {
upperCache->TakeCommitmentFrom(lowerCache,
lowerOldCommitment - lowerNewCommitment);
}
status = upperCache->Commit(lowerOldCommitment, VM_PRIORITY_USER);
if (status != B_OK) {
lowerCache->TakeCommitmentFrom(upperCache, upperCache->Commitment());
upperCache->ReleaseRefAndUnlock();
return status;
}
rw_lock_write_lock(&sAreaCacheLock);
upperCache->TakeAreasFrom(lowerCache);
rw_lock_write_unlock(&sAreaCacheLock);
lowerCache->AddConsumer(upperCache);
if (wiredPagesReservation != NULL) {
for (VMCachePagesTree::Iterator it = lowerCache->pages.GetIterator();
vm_page* page = it.Next();) {
if (page->WiredCount() > 0) {
vm_page* copiedPage = vm_page_allocate_page(
wiredPagesReservation, PAGE_STATE_ACTIVE);
vm_memcpy_physical_page(
copiedPage->physical_page_number * B_PAGE_SIZE,
page->physical_page_number * B_PAGE_SIZE);
upperCache->MovePage(page);
lowerCache->InsertPage(copiedPage,
page->cache_offset * B_PAGE_SIZE);
DEBUG_PAGE_ACCESS_END(copiedPage);
} else {
for (VMArea* tempArea = upperCache->areas.First(); tempArea != NULL;
tempArea = upperCache->areas.GetNext(tempArea)) {
if (!is_page_in_area(tempArea, page))
continue;
addr_t address = virtual_page_address(tempArea, page);
uint32 protection = 0;
uint32 pageProtection = get_area_page_protection(tempArea, address);
if ((pageProtection & B_KERNEL_READ_AREA) != 0)
protection |= B_KERNEL_READ_AREA;
if ((pageProtection & B_READ_AREA) != 0)
protection |= B_READ_AREA;
VMTranslationMap* map
= tempArea->address_space->TranslationMap();
map->Lock();
map->ProtectPage(tempArea, address, protection);
map->Unlock();
}
}
}
} else {
ASSERT(lowerCache->WiredPagesCount() == 0);
for (VMArea* tempArea = upperCache->areas.First(); tempArea != NULL;
tempArea = upperCache->areas.GetNext(tempArea)) {
if (tempArea->page_protections != NULL) {
VMTranslationMap* map = tempArea->address_space->TranslationMap();
map->Lock();
for (VMCachePagesTree::Iterator it = lowerCache->pages.GetIterator();
vm_page* page = it.Next();) {
if (!is_page_in_area(tempArea, page))
continue;
addr_t address = virtual_page_address(tempArea, page);
uint32 protection = 0;
uint32 pageProtection = get_area_page_protection(tempArea, address);
if ((pageProtection & B_KERNEL_READ_AREA) != 0)
protection |= B_KERNEL_READ_AREA;
if ((pageProtection & B_READ_AREA) != 0)
protection |= B_READ_AREA;
map->ProtectPage(tempArea, address, protection);
}
map->Unlock();
continue;
}
uint32 protection = 0;
if ((tempArea->protection & B_KERNEL_READ_AREA) != 0)
protection |= B_KERNEL_READ_AREA;
if ((tempArea->protection & B_READ_AREA) != 0)
protection |= B_READ_AREA;
VMTranslationMap* map = tempArea->address_space->TranslationMap();
map->Lock();
map->ProtectArea(tempArea, protection);
map->Unlock();
}
}
vm_area_put_locked_cache(upperCache);
return B_OK;
}
area_id
vm_copy_area(team_id team, const char* name, void** _address,
uint32 addressSpec, area_id sourceID)
{
MultiAddressSpaceLocker locker;
VMAddressSpace* targetAddressSpace;
VMCache* cache;
VMArea* source;
AreaCacheLocker cacheLocker;
status_t status;
bool sharedArea;
page_num_t wiredPages = 0;
vm_page_reservation wiredPagesReservation;
bool restart;
do {
restart = false;
locker.Unset();
status = locker.AddTeam(team, true, &targetAddressSpace);
if (status == B_OK) {
status = locker.AddAreaCacheAndLock(sourceID, false, false, source,
&cache);
}
if (status != B_OK)
return status;
cacheLocker.SetTo(cache, true);
sharedArea = (source->protection & B_SHARED_AREA) != 0;
page_num_t oldWiredPages = wiredPages;
wiredPages = 0;
if (!sharedArea) {
wiredPages = cache->WiredPagesCount();
if (wiredPages > oldWiredPages) {
cacheLocker.Unlock();
locker.Unlock();
if (oldWiredPages > 0)
vm_page_unreserve_pages(&wiredPagesReservation);
vm_page_reserve_pages(&wiredPagesReservation, wiredPages,
VM_PRIORITY_USER);
restart = true;
}
} else if (oldWiredPages > 0)
vm_page_unreserve_pages(&wiredPagesReservation);
} while (restart);
CObjectDeleter<vm_page_reservation, void, vm_page_unreserve_pages>
pagesUnreserver(wiredPages > 0 ? &wiredPagesReservation : NULL);
bool writableCopy
= (source->protection & (B_KERNEL_WRITE_AREA | B_WRITE_AREA)) != 0;
uint8* targetPageProtections = NULL;
if (source->page_protections != NULL) {
const size_t bytes = area_page_protections_size(source->Size());
targetPageProtections = (uint8*)malloc_etc(bytes,
(source->address_space == VMAddressSpace::Kernel()
|| targetAddressSpace == VMAddressSpace::Kernel())
? HEAP_DONT_LOCK_KERNEL_SPACE : 0);
if (targetPageProtections == NULL)
return B_NO_MEMORY;
memcpy(targetPageProtections, source->page_protections, bytes);
for (size_t i = 0; i < bytes; i++) {
if ((targetPageProtections[i]
& (B_WRITE_AREA | (B_WRITE_AREA << 4))) != 0) {
writableCopy = true;
break;
}
}
}
if (addressSpec == B_CLONE_ADDRESS) {
addressSpec = B_EXACT_ADDRESS;
*_address = (void*)source->Base();
}
VMArea* target;
virtual_address_restrictions addressRestrictions = {};
addressRestrictions.address = *_address;
addressRestrictions.address_specification = addressSpec;
status = map_backing_store(targetAddressSpace, cache, source->cache_offset,
name, source->Size(), source->wiring, source->protection,
source->protection_max,
sharedArea ? REGION_NO_PRIVATE_MAP : REGION_PRIVATE_MAP,
writableCopy ? 0 : CREATE_AREA_DONT_COMMIT_MEMORY,
&addressRestrictions, true, &target, _address);
if (status < B_OK) {
free_etc(targetPageProtections, HEAP_DONT_LOCK_KERNEL_SPACE);
return status;
}
if (targetPageProtections != NULL) {
target->page_protections = targetPageProtections;
if (!sharedArea) {
AreaCacheLocker locker(target);
const size_t newPageCommitment = compute_area_page_commitment(target);
target->cache->Commit(newPageCommitment * B_PAGE_SIZE, VM_PRIORITY_USER);
}
}
if (sharedArea) {
cache->AcquireRefLocked();
}
if (!sharedArea && writableCopy) {
status_t status = vm_copy_on_write_area(cache,
wiredPages > 0 ? &wiredPagesReservation : NULL);
if (status != B_OK) {
cacheLocker.Unlock();
delete_area(targetAddressSpace, target, false);
return status;
}
}
return target->id;
}
status_t
vm_set_area_protection(area_id areaID, uint32 newProtection,
bool kernel)
{
TRACE(("vm_set_area_protection(team = %#" B_PRIx32 ", area = %#" B_PRIx32
", protection = %#" B_PRIx32 ")\n", team, areaID, newProtection));
bool becomesWritable
= (newProtection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0;
MultiAddressSpaceLocker locker;
VMCache* cache;
VMArea* area;
AreaCacheLocker cacheLocker;
status_t status;
team_id areaTeam;
bool isWritable;
bool restart;
do {
restart = false;
locker.Unset();
status = locker.AddAreaCacheAndLock(areaID, true, false, area, &cache);
if (status != B_OK)
return status;
cacheLocker.SetTo(cache, true);
areaTeam = area->address_space->ID();
status = check_protection(areaTeam, &newProtection);
if (status != B_OK)
return status;
if (!kernel && (area->address_space == VMAddressSpace::Kernel()
|| (area->protection & B_KERNEL_AREA) != 0)) {
#if KDEBUG
dprintf("vm_set_area_protection: team %" B_PRId32 " tried to "
"set protection %#" B_PRIx32 " on kernel area %" B_PRId32
" (%s)\n", team_get_current_team_id(), newProtection,
areaID, area->name);
#endif
return B_NOT_ALLOWED;
}
if (!kernel && area->protection_max != 0
&& (newProtection & area->protection_max)
!= (newProtection & B_USER_PROTECTION)) {
#if KDEBUG
dprintf("vm_set_area_protection: team %" B_PRId32 " tried to "
"set protection %#" B_PRIx32 " (max %#" B_PRIx32 ") on area "
"%" B_PRId32 " (%s)\n", team_get_current_team_id(), newProtection,
area->protection_max, areaID, area->name);
#endif
return B_NOT_ALLOWED;
}
if (!kernel && area->address_space->ID() != VMAddressSpace::CurrentID()) {
return B_NOT_ALLOWED;
}
if (area->protection == newProtection)
return B_OK;
isWritable
= (area->protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0;
if (!isWritable && becomesWritable && !cache->consumers.IsEmpty()) {
for (VMArea* otherArea = cache->areas.First(); otherArea != NULL;
otherArea = cache->areas.GetNext(otherArea)) {
if (wait_if_area_is_wired(otherArea, &locker, &cacheLocker)) {
restart = true;
break;
}
}
} else {
if (wait_if_area_is_wired(area, &locker, &cacheLocker))
restart = true;
}
} while (restart);
if (area->page_protections != NULL) {
free_etc(area->page_protections,
area->address_space == VMAddressSpace::Kernel() ? HEAP_DONT_LOCK_KERNEL_SPACE : 0);
area->page_protections = NULL;
isWritable = !becomesWritable;
}
bool changePageProtection = true;
bool changeTopCachePagesOnly = false;
if (isWritable && !becomesWritable) {
if (cache->source != NULL && cache->temporary) {
if (cache->CountWritableAreas(area) == 0) {
status = cache->Commit(cache->page_count * B_PAGE_SIZE,
areaTeam == VMAddressSpace::KernelID()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
}
}
if (newProtection
== (area->protection & ~(B_WRITE_AREA | B_KERNEL_WRITE_AREA))
&& cache->page_count * 2 < area->Size() / B_PAGE_SIZE) {
changeTopCachePagesOnly = true;
}
} else if (!isWritable && becomesWritable) {
if (!cache->consumers.IsEmpty()) {
changePageProtection = false;
status = vm_copy_on_write_area(cache, NULL);
} else {
if (cache->temporary) {
status = cache->Commit(cache->virtual_end - cache->virtual_base,
areaTeam == VMAddressSpace::KernelID()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
}
if (status == B_OK && cache->source != NULL) {
changeTopCachePagesOnly = true;
}
}
} else {
}
if (status == B_OK) {
if (changePageProtection) {
VMTranslationMap* map = area->address_space->TranslationMap();
map->Lock();
if (changeTopCachePagesOnly) {
page_num_t firstPageOffset = area->cache_offset / B_PAGE_SIZE;
page_num_t lastPageOffset
= firstPageOffset + area->Size() / B_PAGE_SIZE;
for (VMCachePagesTree::Iterator it = cache->pages.GetIterator();
vm_page* page = it.Next();) {
if (page->cache_offset >= firstPageOffset
&& page->cache_offset <= lastPageOffset) {
addr_t address = virtual_page_address(area, page);
map->ProtectPage(area, address, newProtection);
}
}
} else
map->ProtectArea(area, newProtection);
map->Unlock();
}
area->protection = newProtection;
}
return status;
}
status_t
vm_get_page_mapping(team_id team, addr_t vaddr, phys_addr_t* paddr)
{
VMAddressSpace* addressSpace = VMAddressSpace::Get(team);
if (addressSpace == NULL)
return B_BAD_TEAM_ID;
VMTranslationMap* map = addressSpace->TranslationMap();
map->Lock();
uint32 dummyFlags;
status_t status = map->Query(vaddr, paddr, &dummyFlags);
map->Unlock();
addressSpace->Put();
return status;
}
bool
vm_test_map_modification(vm_page* page)
{
if (page->modified)
return true;
vm_page_mappings::Iterator iterator = page->mappings.GetIterator();
vm_page_mapping* mapping;
while ((mapping = iterator.Next()) != NULL) {
VMArea* area = mapping->area;
VMTranslationMap* map = area->address_space->TranslationMap();
phys_addr_t physicalAddress;
uint32 flags;
map->Lock();
map->Query(virtual_page_address(area, page), &physicalAddress, &flags);
map->Unlock();
if ((flags & PAGE_MODIFIED) != 0)
return true;
}
return false;
}
void
vm_clear_map_flags(vm_page* page, uint32 flags)
{
if ((flags & PAGE_ACCESSED) != 0)
page->accessed = false;
if ((flags & PAGE_MODIFIED) != 0)
page->modified = false;
vm_page_mappings::Iterator iterator = page->mappings.GetIterator();
vm_page_mapping* mapping;
while ((mapping = iterator.Next()) != NULL) {
VMArea* area = mapping->area;
VMTranslationMap* map = area->address_space->TranslationMap();
map->Lock();
map->ClearFlags(virtual_page_address(area, page), flags);
map->Unlock();
}
}
void
vm_remove_all_page_mappings(vm_page* page)
{
while (vm_page_mapping* mapping = page->mappings.Head()) {
VMArea* area = mapping->area;
VMTranslationMap* map = area->address_space->TranslationMap();
addr_t address = virtual_page_address(area, page);
map->UnmapPage(area, address, false);
}
}
int32
vm_clear_page_mapping_accessed_flags(struct vm_page *page)
{
int32 count = 0;
vm_page_mappings::Iterator iterator = page->mappings.GetIterator();
vm_page_mapping* mapping;
while ((mapping = iterator.Next()) != NULL) {
VMArea* area = mapping->area;
VMTranslationMap* map = area->address_space->TranslationMap();
bool modified;
if (map->ClearAccessedAndModified(area,
virtual_page_address(area, page), false, modified)) {
count++;
}
page->modified |= modified;
}
if (page->accessed) {
count++;
page->accessed = false;
}
return count;
}
int32
vm_remove_all_page_mappings_if_unaccessed(struct vm_page *page)
{
ASSERT(page->WiredCount() == 0);
if (page->accessed)
return vm_clear_page_mapping_accessed_flags(page);
while (vm_page_mapping* mapping = page->mappings.Head()) {
VMArea* area = mapping->area;
VMTranslationMap* map = area->address_space->TranslationMap();
addr_t address = virtual_page_address(area, page);
bool modified = false;
if (map->ClearAccessedAndModified(area, address, true, modified)) {
page->accessed = true;
page->modified |= modified;
return vm_clear_page_mapping_accessed_flags(page);
}
page->modified |= modified;
}
return 0;
}
void
vm_delete_areas(struct VMAddressSpace* addressSpace, bool deletingAddressSpace)
{
TRACE(("vm_delete_areas: called on address space 0x%" B_PRIx32 "\n",
addressSpace->ID()));
addressSpace->WriteLock();
addressSpace->UnreserveAllAddressRanges(0);
VMAreas::WriteLock();
{
VMAddressSpace::AreaIterator it = addressSpace->GetAreaIterator();
while (VMArea* area = it.Next())
VMAreas::Remove(area);
}
VMAreas::WriteUnlock();
while (VMArea* area = addressSpace->FirstArea()) {
ASSERT(!area->IsWired());
delete_area(addressSpace, area, deletingAddressSpace, true);
}
addressSpace->WriteUnlock();
}
static area_id
vm_area_for(addr_t address, bool kernel)
{
team_id team;
if (IS_USER_ADDRESS(address)) {
team = VMAddressSpace::CurrentID();
if (team < 0)
return team;
} else
team = VMAddressSpace::KernelID();
AddressSpaceReadLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
VMArea* area = locker.AddressSpace()->LookupArea(address);
if (area != NULL) {
if (!kernel && team == VMAddressSpace::KernelID()
&& (area->protection & (B_READ_AREA | B_WRITE_AREA | B_CLONEABLE_AREA)) == 0)
return B_ERROR;
return area->id;
}
return B_ERROR;
}
static void
unreserve_boot_loader_ranges(kernel_args* args)
{
TRACE(("unreserve_boot_loader_ranges()\n"));
for (uint32 i = 0; i < args->num_virtual_allocated_ranges; i++) {
const addr_range& range = args->virtual_allocated_range[i];
vm_unreserve_address_range(VMAddressSpace::KernelID(),
(void*)(addr_t)range.start, range.size);
}
}
static void
reserve_boot_loader_ranges(kernel_args* args)
{
TRACE(("reserve_boot_loader_ranges()\n"));
for (uint32 i = 0; i < args->num_virtual_allocated_ranges; i++) {
const addr_range& range = args->virtual_allocated_range[i];
void* address = (void*)(addr_t)range.start;
if (!IS_KERNEL_ADDRESS(address)) {
dprintf("reserve_boot_loader_ranges(): Skipping range: %p, %"
B_PRIu64 "\n", address, range.size);
continue;
}
status_t status = vm_reserve_address_range(VMAddressSpace::KernelID(),
&address, B_EXACT_ADDRESS, range.size, 0);
if (status < B_OK)
panic("could not reserve boot loader ranges\n");
}
}
status_t
vm_init(kernel_args* args)
{
status_t err = 0;
TRACE(("vm_init: entry\n"));
err = arch_vm_translation_map_init(args, &sPhysicalPageMapper);
err = arch_vm_init(args);
vm_page_init_num_pages(args);
slab_init(args);
heap_init(args);
vm_page_init(args);
vm_cache_init(args);
{
status_t error = VMAreas::Init();
if (error != B_OK)
panic("vm_init: error initializing areas map\n");
}
VMAddressSpace::Init();
reserve_boot_loader_ranges(args);
#if DEBUG_HEAPS
heap_init_post_area();
#endif
arch_vm_translation_map_init_post_area(args);
arch_vm_init_post_area(args);
vm_page_init_post_area(args);
slab_init_post_area();
vm_kernel_args_init_post_area(args);
void* lastPage = (void*)ROUNDDOWN(~(addr_t)0, B_PAGE_SIZE);
vm_block_address_range("overflow protection", lastPage, B_PAGE_SIZE);
#if PARANOID_KERNEL_MALLOC
addr_t blockAddress = 0xcccccccc;
if (blockAddress < KERNEL_BASE)
blockAddress |= KERNEL_BASE;
vm_block_address_range("uninitialized heap memory",
(void *)ROUNDDOWN(blockAddress, B_PAGE_SIZE), B_PAGE_SIZE * 64);
#if B_HAIKU_64_BIT
blockAddress = 0xcccccccccccccccc;
if (blockAddress < KERNEL_BASE)
blockAddress |= KERNEL_BASE;
vm_block_address_range("uninitialized heap memory",
(void *)ROUNDDOWN(blockAddress, B_PAGE_SIZE), B_PAGE_SIZE * 64);
#endif
#endif
#if PARANOID_KERNEL_FREE
blockAddress = 0xdeadbeef;
if (blockAddress < KERNEL_BASE)
blockAddress |= KERNEL_BASE;
vm_block_address_range("freed heap memory",
(void *)ROUNDDOWN(blockAddress, B_PAGE_SIZE), B_PAGE_SIZE * 64);
#if B_HAIKU_64_BIT
blockAddress = 0xdeadbeefdeadbeef;
if (blockAddress < KERNEL_BASE)
blockAddress |= KERNEL_BASE;
vm_block_address_range("freed heap memory",
(void *)ROUNDDOWN(blockAddress, B_PAGE_SIZE), B_PAGE_SIZE * 64);
#endif
#endif
create_page_mappings_object_caches();
vm_debug_init();
TRACE(("vm_init: exit\n"));
vm_cache_init_post_heap();
return err;
}
status_t
vm_init_post_sem(kernel_args* args)
{
arch_vm_init_end(args);
unreserve_boot_loader_ranges(args);
arch_vm_translation_map_init_post_sem(args);
slab_init_post_sem();
heap_init_post_sem();
return B_OK;
}
status_t
vm_init_post_thread(kernel_args* args)
{
vm_page_init_post_thread(args);
slab_init_post_thread();
deferred_free_init();
return heap_init_post_thread();
}
status_t
vm_init_post_modules(kernel_args* args)
{
return arch_vm_init_post_modules(args);
}
void
permit_page_faults()
{
thread_get_current_thread()->page_faults_allowed++;
}
void
forbid_page_faults()
{
thread_get_current_thread()->page_faults_allowed--;
}
status_t
vm_page_fault(addr_t address, addr_t faultAddress, bool isWrite, bool isExecute,
bool isUser, addr_t* newIP)
{
FTRACE(("vm_page_fault: page fault at 0x%lx, ip 0x%lx\n", address,
faultAddress));
TPF(PageFaultStart(address, isWrite, isUser, faultAddress));
addr_t pageAddress = ROUNDDOWN(address, B_PAGE_SIZE);
VMAddressSpace* addressSpace = NULL;
status_t status = B_OK;
*newIP = 0;
atomic_add((int32*)&sPageFaults, 1);
if (IS_KERNEL_ADDRESS(pageAddress)) {
addressSpace = VMAddressSpace::GetKernel();
} else if (IS_USER_ADDRESS(pageAddress)) {
addressSpace = VMAddressSpace::GetCurrent();
if (addressSpace == NULL) {
if (!isUser) {
dprintf("vm_page_fault: kernel thread accessing invalid user "
"memory!\n");
status = B_BAD_ADDRESS;
TPF(PageFaultError(-1,
VMPageFaultTracing
::PAGE_FAULT_ERROR_KERNEL_BAD_USER_MEMORY));
} else {
panic("vm_page_fault: non kernel thread accessing user memory "
"that doesn't exist!\n");
status = B_BAD_ADDRESS;
}
}
} else {
status = B_BAD_ADDRESS;
TPF(PageFaultError(-1,
VMPageFaultTracing::PAGE_FAULT_ERROR_NO_ADDRESS_SPACE));
}
if (status == B_OK) {
status = vm_soft_fault(addressSpace, pageAddress, isWrite, isExecute,
isUser, NULL);
}
if (status < B_OK) {
if (!isUser) {
dprintf("vm_page_fault: vm_soft_fault returned error '%s' on fault at "
"0x%lx, ip 0x%lx, write %d, kernel, exec %d, thread %" B_PRId32 "\n",
strerror(status), address, faultAddress, isWrite, isExecute,
thread_get_current_thread_id());
Thread* thread = thread_get_current_thread();
if (thread != NULL && thread->fault_handler != 0) {
*newIP = reinterpret_cast<uintptr_t>(thread->fault_handler);
} else {
panic("vm_page_fault: unhandled page fault in kernel space at "
"0x%lx, ip 0x%lx\n", address, faultAddress);
}
} else {
Thread* thread = thread_get_current_thread();
#ifdef TRACE_FAULTS
VMArea* area = NULL;
if (addressSpace != NULL) {
addressSpace->ReadLock();
area = addressSpace->LookupArea(faultAddress);
}
dprintf("vm_page_fault: thread \"%s\" (%" B_PRId32 ") in team "
"\"%s\" (%" B_PRId32 ") tried to %s address %#lx, ip %#lx "
"(\"%s\" +%#lx)\n", thread->name, thread->id,
thread->team->Name(), thread->team->id,
isWrite ? "write" : (isExecute ? "execute" : "read"), address,
faultAddress, area ? area->name : "???", faultAddress - (area ?
area->Base() : 0x0));
if (addressSpace != NULL)
addressSpace->ReadUnlock();
#endif
struct sigaction action;
if ((sigaction(SIGSEGV, NULL, &action) == 0
&& action.sa_handler != SIG_DFL
&& action.sa_handler != SIG_IGN)
|| user_debug_exception_occurred(B_SEGMENT_VIOLATION,
SIGSEGV)) {
Signal signal(SIGSEGV,
status == B_PERMISSION_DENIED
? SEGV_ACCERR : SEGV_MAPERR,
EFAULT, thread->team->id);
signal.SetAddress((void*)address);
send_signal_to_thread(thread, signal, 0);
}
}
}
if (addressSpace != NULL)
addressSpace->Put();
return B_HANDLED_INTERRUPT;
}
struct PageFaultContext {
AddressSpaceReadLocker addressSpaceLocker;
VMCacheChainLocker cacheChainLocker;
VMTranslationMap* map;
VMCache* topCache;
off_t cacheOffset;
vm_page_reservation reservation;
bool isWrite;
vm_page* page;
bool restart;
bool pageAllocated;
PageFaultContext(VMAddressSpace* addressSpace, bool isWrite)
:
addressSpaceLocker(addressSpace, true),
map(addressSpace->TranslationMap()),
isWrite(isWrite)
{
}
~PageFaultContext()
{
UnlockAll();
vm_page_unreserve_pages(&reservation);
}
void Prepare(VMCache* topCache, off_t cacheOffset)
{
this->topCache = topCache;
this->cacheOffset = cacheOffset;
page = NULL;
restart = false;
pageAllocated = false;
cacheChainLocker.SetTo(topCache);
}
void UnlockAll(VMCache* exceptCache = NULL)
{
topCache = NULL;
addressSpaceLocker.Unlock();
cacheChainLocker.Unlock(exceptCache);
}
};
static status_t
fault_get_page(PageFaultContext& context)
{
VMCache* cache = context.topCache;
vm_page* page = NULL;
while (cache != NULL) {
page = cache->LookupPage(context.cacheOffset);
if (page != NULL && page->busy) {
if (thread_get_current_thread()->page_fault_waits_allowed < 1)
return B_BUSY;
context.UnlockAll(cache);
cache->ReleaseRefLocked();
cache->WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, false);
context.restart = true;
return B_OK;
}
if (page != NULL)
break;
if (cache->StoreHasPage(context.cacheOffset)) {
page = vm_page_allocate_page(&context.reservation,
PAGE_STATE_ACTIVE | VM_PAGE_ALLOC_BUSY);
cache->InsertPage(page, context.cacheOffset);
DEBUG_PAGE_ACCESS_END(page);
cache->AcquireRefLocked();
context.UnlockAll();
generic_io_vec vec;
vec.base = (phys_addr_t)page->physical_page_number * B_PAGE_SIZE;
generic_size_t bytesRead = vec.length = B_PAGE_SIZE;
status_t status = cache->Read(context.cacheOffset, &vec, 1,
B_PHYSICAL_IO_REQUEST, &bytesRead);
cache->Lock();
DEBUG_PAGE_ACCESS_START(page);
if (status < B_OK) {
dprintf("reading page from cache %p returned: %s!\n",
cache, strerror(status));
cache->NotifyPageEvents(page, PAGE_EVENT_NOT_BUSY);
cache->RemovePage(page);
vm_page_free_etc(cache, page, &context.reservation);
cache->ReleaseRefAndUnlock();
return status;
}
cache->MarkPageUnbusy(page);
DEBUG_PAGE_ACCESS_END(page);
cache->ReleaseRefAndUnlock();
context.restart = true;
return B_OK;
}
cache = context.cacheChainLocker.LockSourceCache();
}
if (page == NULL) {
cache = context.topCache;
context.cacheChainLocker.Unlock(context.topCache);
context.cacheChainLocker.SetTo(context.topCache);
page = vm_page_allocate_page(&context.reservation,
PAGE_STATE_ACTIVE | VM_PAGE_ALLOC_CLEAR);
FTRACE(("vm_soft_fault: just allocated page 0x%" B_PRIxPHYSADDR "\n",
page->physical_page_number));
cache->InsertPage(page, context.cacheOffset);
context.pageAllocated = true;
} else if (page->Cache() != context.topCache && context.isWrite) {
vm_page* sourcePage = page;
FTRACE(("get new page, copy it, and put it into the topmost cache\n"));
page = vm_page_allocate_page(&context.reservation, PAGE_STATE_ACTIVE);
sourcePage->busy = true;
context.cacheChainLocker.UnlockKeepRefs(true);
vm_memcpy_physical_page(page->physical_page_number * B_PAGE_SIZE,
sourcePage->physical_page_number * B_PAGE_SIZE);
context.cacheChainLocker.RelockCaches(true);
sourcePage->Cache()->MarkPageUnbusy(sourcePage);
sourcePage->Cache()->IncrementCopiedPagesCount();
context.topCache->InsertPage(page, context.cacheOffset);
context.pageAllocated = true;
} else
DEBUG_PAGE_ACCESS_START(page);
context.page = page;
return B_OK;
}
static status_t
vm_soft_fault(VMAddressSpace* addressSpace, addr_t originalAddress,
bool isWrite, bool isExecute, bool isUser, vm_page** wirePage)
{
FTRACE(("vm_soft_fault: thid 0x%" B_PRIx32 " address 0x%" B_PRIxADDR ", "
"isWrite %d, isUser %d\n", thread_get_current_thread_id(),
originalAddress, isWrite, isUser));
PageFaultContext context(addressSpace, isWrite);
addr_t address = ROUNDDOWN(originalAddress, B_PAGE_SIZE);
status_t status = B_OK;
addressSpace->IncrementFaultCount();
size_t reservePages = 2 + context.map->MaxPagesNeededToMap(originalAddress,
originalAddress);
context.addressSpaceLocker.Unlock();
vm_page_reserve_pages(&context.reservation, reservePages,
addressSpace == VMAddressSpace::Kernel()
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER);
#ifdef TRACE_FAULTS
const bool logFaults = true;
#else
const bool logFaults = !isUser;
#endif
while (true) {
context.addressSpaceLocker.Lock();
VMArea* area = addressSpace->LookupArea(address);
if (area == NULL) {
if (logFaults) {
dprintf("vm_soft_fault: va 0x%lx not covered by area in address "
"space\n", originalAddress);
}
TPF(PageFaultError(-1,
VMPageFaultTracing::PAGE_FAULT_ERROR_NO_AREA));
status = B_BAD_ADDRESS;
break;
}
uint32 protection = get_area_page_protection(area, address);
if (isUser && (protection & B_USER_PROTECTION) == 0
&& (area->protection & B_KERNEL_AREA) != 0) {
if (logFaults) {
dprintf("user access on kernel area 0x%" B_PRIx32 " at %p\n",
area->id, (void*)originalAddress);
}
TPF(PageFaultError(area->id,
VMPageFaultTracing::PAGE_FAULT_ERROR_KERNEL_ONLY));
status = B_PERMISSION_DENIED;
break;
}
if (isWrite && (protection
& (B_WRITE_AREA | (isUser ? 0 : B_KERNEL_WRITE_AREA))) == 0) {
if (logFaults) {
dprintf("write access attempted on write-protected area 0x%"
B_PRIx32 " at %p\n", area->id, (void*)originalAddress);
}
TPF(PageFaultError(area->id,
VMPageFaultTracing::PAGE_FAULT_ERROR_WRITE_PROTECTED));
status = B_PERMISSION_DENIED;
break;
} else if (isExecute && (protection
& (B_EXECUTE_AREA | (isUser ? 0 : B_KERNEL_EXECUTE_AREA))) == 0) {
if (logFaults) {
dprintf("instruction fetch attempted on execute-protected area 0x%"
B_PRIx32 " at %p\n", area->id, (void*)originalAddress);
}
TPF(PageFaultError(area->id,
VMPageFaultTracing::PAGE_FAULT_ERROR_EXECUTE_PROTECTED));
status = B_PERMISSION_DENIED;
break;
} else if (!isWrite && !isExecute && (protection
& (B_READ_AREA | (isUser ? 0 : B_KERNEL_READ_AREA))) == 0) {
if (logFaults) {
dprintf("read access attempted on read-protected area 0x%" B_PRIx32
" at %p\n", area->id, (void*)originalAddress);
}
TPF(PageFaultError(area->id,
VMPageFaultTracing::PAGE_FAULT_ERROR_READ_PROTECTED));
status = B_PERMISSION_DENIED;
break;
}
context.Prepare(vm_area_get_locked_cache(area),
address - area->Base() + area->cache_offset);
{
status = context.topCache->Fault(addressSpace, context.cacheOffset);
if (status != B_BAD_HANDLER)
break;
}
status = fault_get_page(context);
if (status != B_OK) {
TPF(PageFaultError(area->id, status));
break;
}
if (context.restart)
continue;
TPF(PageFaultDone(area->id, context.topCache, context.page->Cache(),
context.page));
uint32 newProtection = protection;
if (context.page->Cache() != context.topCache && !isWrite)
newProtection &= ~(B_WRITE_AREA | B_KERNEL_WRITE_AREA);
bool unmapPage = false;
bool mapPage = true;
context.map->Lock();
phys_addr_t physicalAddress;
uint32 flags;
vm_page* mappedPage = NULL;
if (context.map->Query(address, &physicalAddress, &flags) == B_OK
&& (flags & PAGE_PRESENT) != 0
&& (mappedPage = vm_lookup_page(physicalAddress / B_PAGE_SIZE))
!= NULL) {
if (mappedPage == context.page) {
context.map->ProtectPage(area, address, newProtection);
mapPage = false;
} else
unmapPage = true;
}
context.map->Unlock();
if (unmapPage) {
VMAreaUnwiredWaiter waiter;
if (area->AddWaiterIfWired(&waiter, address, B_PAGE_SIZE,
VMArea::IGNORE_WRITE_WIRED_RANGES)) {
if (context.pageAllocated) {
context.topCache->RemovePage(context.page);
vm_page_free_etc(context.topCache, context.page,
&context.reservation);
} else
DEBUG_PAGE_ACCESS_END(context.page);
context.UnlockAll();
waiter.waitEntry.Wait();
continue;
}
DEBUG_PAGE_ACCESS_START(mappedPage);
unmap_page(area, address);
DEBUG_PAGE_ACCESS_END(mappedPage);
}
if (mapPage) {
if (map_page(area, context.page, address, newProtection,
&context.reservation) != B_OK) {
DEBUG_PAGE_ACCESS_END(context.page);
context.UnlockAll();
if (object_cache_reserve(page_mapping_object_cache_for(
context.page->physical_page_number), 1, 0)
!= B_OK) {
panic("vm_soft_fault: failed to allocate mapping object for page %p",
context.page);
status = B_NO_MEMORY;
} else if (wirePage != NULL) {
continue;
}
break;
}
} else if (context.page->State() == PAGE_STATE_INACTIVE)
vm_page_set_state(context.page, PAGE_STATE_ACTIVE);
if (wirePage != NULL && status == B_OK) {
increment_page_wired_count(context.page);
*wirePage = context.page;
}
context.page->Cache()->IncrementFaultCount();
DEBUG_PAGE_ACCESS_END(context.page);
break;
}
return status;
}
status_t
vm_get_physical_page(phys_addr_t paddr, addr_t* _vaddr, void** _handle)
{
return sPhysicalPageMapper->GetPage(paddr, _vaddr, _handle);
}
status_t
vm_put_physical_page(addr_t vaddr, void* handle)
{
return sPhysicalPageMapper->PutPage(vaddr, handle);
}
status_t
vm_get_physical_page_current_cpu(phys_addr_t paddr, addr_t* _vaddr,
void** _handle)
{
return sPhysicalPageMapper->GetPageCurrentCPU(paddr, _vaddr, _handle);
}
status_t
vm_put_physical_page_current_cpu(addr_t vaddr, void* handle)
{
return sPhysicalPageMapper->PutPageCurrentCPU(vaddr, handle);
}
status_t
vm_get_physical_page_debug(phys_addr_t paddr, addr_t* _vaddr, void** _handle)
{
return sPhysicalPageMapper->GetPageDebug(paddr, _vaddr, _handle);
}
status_t
vm_put_physical_page_debug(addr_t vaddr, void* handle)
{
return sPhysicalPageMapper->PutPageDebug(vaddr, handle);
}
static off_t
vm_available_memory_locked()
{
#if ENABLE_SWAP_SUPPORT
return std::min(sAvailableMemory, sAvailableMemoryAndSwap);
#else
return sAvailableMemory;
#endif
}
void
vm_get_info(system_info* info)
{
swap_get_info(info);
InterruptsWriteSpinLocker locker(sAvailableMemoryLock);
info->needed_memory = sNeededMemory;
info->free_memory = vm_available_memory_locked();
}
uint32
vm_num_page_faults(void)
{
return sPageFaults;
}
off_t
vm_available_memory(void)
{
InterruptsWriteSpinLocker locker(sAvailableMemoryLock);
return vm_available_memory_locked();
}
off_t
vm_available_memory_debug(void)
{
return sAvailableMemory;
}
off_t
vm_available_not_needed_memory(void)
{
InterruptsWriteSpinLocker locker(sAvailableMemoryLock);
return vm_available_memory_locked() - sNeededMemory;
}
off_t
vm_available_not_needed_memory_debug(void)
{
return vm_available_memory_locked() - sNeededMemory;
}
size_t
vm_kernel_address_space_left(void)
{
return VMAddressSpace::Kernel()->FreeSpace();
}
void
vm_unreserve_memory(size_t amount)
{
ASSERT((amount % B_PAGE_SIZE) == 0);
if (amount == 0)
return;
InterruptsReadSpinLocker readLocker(sAvailableMemoryLock);
#if ENABLE_SWAP_SUPPORT
atomic_add64(&sAvailableMemoryAndSwap, amount);
#endif
atomic_add64(&sAvailableMemory, amount);
}
#if ENABLE_SWAP_SUPPORT
void
vm_unreserve_memory_or_swap(size_t amount)
{
ASSERT((amount % B_PAGE_SIZE) == 0);
if (amount == 0)
return;
InterruptsReadSpinLocker readLocker(sAvailableMemoryLock);
atomic_add64(&sAvailableMemoryAndSwap, amount);
}
#endif
static status_t
vm_try_reserve_internal(off_t& pool, uint32 resource,
size_t amount, int priority, bigtime_t absoluteTimeout)
{
ASSERT((amount % B_PAGE_SIZE) == 0);
ASSERT(priority >= 0 && priority < (int)B_COUNT_OF(kMemoryReserveForPriority));
TRACE(("try to reserve %lu bytes, %Lu left\n", amount, pool));
const size_t reserve = kMemoryReserveForPriority[priority];
const off_t amountPlusReserve = amount + reserve;
InterruptsReadSpinLocker readLocker(sAvailableMemoryLock);
if (atomic_get64(&pool) > (off_t)(amountPlusReserve + amount)) {
if (atomic_add64(&pool, -amount) >= amountPlusReserve)
return B_OK;
atomic_add64(&pool, amount);
}
readLocker.Unlock();
InterruptsWriteSpinLocker writeLocker(sAvailableMemoryLock);
if (pool >= amountPlusReserve) {
pool -= amount;
return B_OK;
}
if (absoluteTimeout <= system_time())
return B_NO_MEMORY;
int32 retries = 3;
do {
sNeededMemory += amount;
uint64 requirement = sNeededMemory - (pool - reserve);
writeLocker.Unlock();
low_resource(resource, requirement,
B_ABSOLUTE_TIMEOUT, absoluteTimeout);
writeLocker.Lock();
sNeededMemory -= amount;
if (pool >= amountPlusReserve) {
pool -= amount;
return B_OK;
}
} while (--retries > 0 && absoluteTimeout > system_time());
return B_NO_MEMORY;
}
status_t
vm_try_reserve_memory(size_t amount, int priority, bigtime_t timeout)
{
timeout += system_time();
#if ENABLE_SWAP_SUPPORT
status_t status = vm_try_reserve_internal(sAvailableMemoryAndSwap, B_KERNEL_RESOURCE_MEMORY,
amount, priority, timeout);
if (status != B_OK)
return status;
status = vm_try_reserve_internal(sAvailableMemory, B_KERNEL_RESOURCE_MEMORY,
amount, priority, timeout);
if (status != B_OK)
vm_unreserve_memory_or_swap(amount);
return status;
#else
return vm_try_reserve_internal(sAvailableMemory, B_KERNEL_RESOURCE_MEMORY,
amount, priority, timeout);
#endif
}
#if ENABLE_SWAP_SUPPORT
status_t
vm_try_reserve_memory_or_swap(size_t amount, int priority, bigtime_t timeout)
{
timeout += system_time();
return vm_try_reserve_internal(sAvailableMemoryAndSwap, B_KERNEL_RESOURCE_MEMORY,
amount, priority, timeout);
}
#endif
status_t
vm_set_area_memory_type(area_id id, phys_addr_t physicalBase, uint32 type)
{
AddressSpaceReadLocker locker;
VMArea* area;
status_t status = locker.SetFromArea(id, area);
if (status != B_OK)
return status;
uint32 oldType = area->MemoryType();
if (type == oldType)
return B_OK;
VMTranslationMap* map = area->address_space->TranslationMap();
map->Lock();
area->SetMemoryType(type);
map->ProtectArea(area, area->protection);
map->Unlock();
status_t error = arch_vm_set_memory_type(area, physicalBase, type, NULL);
if (error != B_OK) {
map->Lock();
area->SetMemoryType(oldType);
map->ProtectArea(area, area->protection);
map->Unlock();
return error;
}
return B_OK;
}
static void
fix_protection(uint32* protection)
{
if ((*protection & B_KERNEL_EXECUTE_AREA) != 0
&& ((*protection & B_KERNEL_WRITE_AREA) != 0
|| (*protection & B_WRITE_AREA) != 0)
&& !gKernelStartup)
panic("kernel areas cannot be both writable and executable!");
if ((*protection & B_KERNEL_PROTECTION) == 0) {
if ((*protection & B_WRITE_AREA) != 0)
*protection |= B_KERNEL_WRITE_AREA;
if ((*protection & B_READ_AREA) != 0)
*protection |= B_KERNEL_READ_AREA;
}
}
static status_t
check_protection(team_id& team, uint32* protection)
{
if (team == B_CURRENT_TEAM)
team = VMAddressSpace::CurrentID();
if (team < 0)
return B_BAD_TEAM_ID;
fix_protection(protection);
if (!arch_vm_supports_protection(team, *protection))
return B_NOT_SUPPORTED;
return B_OK;
}
static void
fill_area_info(struct VMArea* area, area_info* info, size_t size)
{
strlcpy(info->name, area->name, B_OS_NAME_LENGTH);
info->area = area->id;
info->address = (void*)area->Base();
info->size = area->Size();
info->protection = area->protection;
info->lock = area->wiring;
info->team = area->address_space->ID();
VMCache* cache = vm_area_get_locked_cache(area);
info->ram_size = cache->page_count * B_PAGE_SIZE;
info->copy_count = cache->CopiedPagesCount();
info->in_count = 0;
info->out_count = 0;
vm_area_put_locked_cache(cache);
}
static status_t
vm_resize_area(area_id areaID, size_t newSize, bool kernel)
{
if ((newSize & (B_PAGE_SIZE - 1)) != 0)
return B_BAD_VALUE;
VMArea* area;
VMCache* cache;
MultiAddressSpaceLocker locker;
AreaCacheLocker cacheLocker;
status_t status;
size_t oldSize;
bool anyKernelArea;
bool restart;
do {
anyKernelArea = false;
restart = false;
locker.Unset();
status = locker.AddAreaCacheAndLock(areaID, true, true, area, &cache);
if (status != B_OK)
return status;
cacheLocker.SetTo(cache, true);
const team_id team = team_get_current_team_id();
if (!kernel && (area->address_space == VMAddressSpace::Kernel()
|| (area->protection & B_KERNEL_AREA) != 0)) {
dprintf("vm_resize_area: team %" B_PRId32 " tried to "
"resize kernel area %" B_PRId32 " (%s)\n",
team, areaID, area->name);
return B_NOT_ALLOWED;
}
if (!kernel && area->address_space->ID() != team) {
return B_NOT_ALLOWED;
}
oldSize = area->Size();
if (newSize == oldSize)
return B_OK;
if (cache->type != CACHE_TYPE_RAM)
return B_NOT_ALLOWED;
if (oldSize < newSize) {
for (VMArea* current = cache->areas.First(); current != NULL;
current = cache->areas.GetNext(current)) {
if (!current->address_space->CanResizeArea(current, newSize))
return B_ERROR;
anyKernelArea
|= current->address_space == VMAddressSpace::Kernel();
}
} else {
for (VMArea* current = cache->areas.First(); current != NULL;
current = cache->areas.GetNext(current)) {
anyKernelArea
|= current->address_space == VMAddressSpace::Kernel();
if (wait_if_area_range_is_wired(current,
current->Base() + newSize, oldSize - newSize, &locker,
&cacheLocker)) {
restart = true;
break;
}
}
}
} while (restart);
int priority = kernel && anyKernelArea
? VM_PRIORITY_SYSTEM : VM_PRIORITY_USER;
uint32 allocationFlags = kernel && anyKernelArea
? HEAP_DONT_WAIT_FOR_MEMORY | HEAP_DONT_LOCK_KERNEL_SPACE : 0;
if (oldSize < newSize) {
status = cache->Resize(cache->virtual_base + newSize, priority);
if (status != B_OK)
return status;
}
for (VMArea* current = cache->areas.First(); current != NULL;
current = cache->areas.GetNext(current)) {
status = current->address_space->ResizeArea(current, newSize,
allocationFlags);
if (status != B_OK)
break;
if (newSize < oldSize) {
VMCacheChainLocker cacheChainLocker(cache);
cacheChainLocker.LockAllSourceCaches();
unmap_pages(current, current->Base() + newSize,
oldSize - newSize);
cacheChainLocker.Unlock(cache);
}
}
if (status == B_OK) {
if (area->page_protections != NULL) {
size_t bytes = area_page_protections_size(newSize);
uint8* newProtections
= (uint8*)realloc(area->page_protections, bytes);
if (newProtections == NULL)
status = B_NO_MEMORY;
else {
area->page_protections = newProtections;
if (oldSize < newSize) {
uint32 offset = area_page_protections_size(oldSize);
uint32 areaProtection = area->protection
& (B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA);
memset(area->page_protections + offset,
areaProtection | (areaProtection << 4), bytes - offset);
if ((oldSize / B_PAGE_SIZE) % 2 != 0) {
uint8& entry = area->page_protections[offset - 1];
entry = (entry & 0x0f) | (areaProtection << 4);
}
}
}
}
}
if (status == B_OK && newSize < oldSize)
status = cache->Resize(cache->virtual_base + newSize, priority);
if (status != B_OK) {
for (VMArea* current = cache->areas.First(); current != NULL;
current = cache->areas.GetNext(current)) {
if (current->address_space->ResizeArea(current, oldSize,
allocationFlags) != B_OK) {
panic("vm_resize_area(): Failed and not being able to restore "
"original state.");
}
}
cache->Resize(cache->virtual_base + oldSize, priority);
}
return status;
}
status_t
vm_memset_physical(phys_addr_t address, int value, phys_size_t length)
{
return sPhysicalPageMapper->MemsetPhysical(address, value, length);
}
status_t
vm_memcpy_from_physical(void* to, phys_addr_t from, size_t length, bool user)
{
return sPhysicalPageMapper->MemcpyFromPhysical(to, from, length, user);
}
status_t
vm_memcpy_to_physical(phys_addr_t to, const void* _from, size_t length,
bool user)
{
return sPhysicalPageMapper->MemcpyToPhysical(to, _from, length, user);
}
void
vm_memcpy_physical_page(phys_addr_t to, phys_addr_t from)
{
return sPhysicalPageMapper->MemcpyPhysicalPage(to, from);
}
static inline bool
validate_memory_range(const void* addr, size_t size)
{
addr_t address = (addr_t)addr;
if ((address + size) < address)
return false;
return IS_USER_ADDRESS(address) == IS_USER_ADDRESS(address + size - 1);
}
status_t
user_memcpy(void* to, const void* from, size_t size)
{
if (!validate_memory_range(to, size) || !validate_memory_range(from, size))
return B_BAD_ADDRESS;
if (arch_cpu_user_memcpy(to, from, size) < B_OK)
return B_BAD_ADDRESS;
return B_OK;
}
ssize_t
user_strlcpy(char* to, const char* from, size_t size)
{
if (to == NULL && size != 0)
return B_BAD_VALUE;
if (from == NULL)
return B_BAD_ADDRESS;
size_t maxSize = size;
if ((addr_t)from + maxSize < (addr_t)from)
maxSize -= (addr_t)from + maxSize;
if (IS_USER_ADDRESS(from) && !IS_USER_ADDRESS((addr_t)from + maxSize))
maxSize = USER_TOP - (addr_t)from;
if (!validate_memory_range(to, maxSize))
return B_BAD_ADDRESS;
ssize_t result = arch_cpu_user_strlcpy(to, from, maxSize);
if (result < 0)
return result;
if ((size_t)result >= maxSize && maxSize < size)
return B_BAD_ADDRESS;
return result;
}
status_t
user_memset(void* s, char c, size_t count)
{
if (!validate_memory_range(s, count))
return B_BAD_ADDRESS;
if (arch_cpu_user_memset(s, c, count) < B_OK)
return B_BAD_ADDRESS;
return B_OK;
}
status_t
vm_wire_page(team_id team, addr_t address, bool writable,
VMPageWiringInfo* info)
{
addr_t pageAddress = ROUNDDOWN((addr_t)address, B_PAGE_SIZE);
info->range.SetTo(pageAddress, B_PAGE_SIZE, writable, false);
bool isUser = IS_USER_ADDRESS(address);
uint32 requiredProtection = PAGE_PRESENT
| B_KERNEL_READ_AREA | (isUser ? B_READ_AREA : 0);
if (writable)
requiredProtection |= B_KERNEL_WRITE_AREA | (isUser ? B_WRITE_AREA : 0);
VMAddressSpace* addressSpace = NULL;
if (isUser) {
if (team == B_CURRENT_TEAM)
addressSpace = VMAddressSpace::GetCurrent();
else
addressSpace = VMAddressSpace::Get(team);
} else
addressSpace = VMAddressSpace::GetKernel();
if (addressSpace == NULL)
return B_ERROR;
AddressSpaceReadLocker addressSpaceLocker(addressSpace, true);
VMTranslationMap* map = addressSpace->TranslationMap();
status_t error = B_OK;
VMArea* area = addressSpace->LookupArea(pageAddress);
if (area == NULL) {
addressSpace->Put();
return B_BAD_ADDRESS;
}
VMCacheChainLocker cacheChainLocker(vm_area_get_locked_cache(area));
area->Wire(&info->range);
cacheChainLocker.LockAllSourceCaches();
map->Lock();
phys_addr_t physicalAddress;
uint32 flags;
vm_page* page;
if (map->Query(pageAddress, &physicalAddress, &flags) == B_OK
&& (flags & requiredProtection) == requiredProtection
&& (page = vm_lookup_page(physicalAddress / B_PAGE_SIZE))
!= NULL) {
increment_page_wired_count(page);
map->Unlock();
cacheChainLocker.Unlock();
addressSpaceLocker.Unlock();
} else {
map->Unlock();
cacheChainLocker.Unlock();
addressSpaceLocker.Unlock();
error = vm_soft_fault(addressSpace, pageAddress, writable, false,
isUser, &page);
if (error != B_OK) {
VMCache* cache = vm_area_get_locked_cache(area);
area->Unwire(&info->range);
cache->ReleaseRefAndUnlock();
addressSpace->Put();
return error;
}
}
info->physicalAddress
= (phys_addr_t)page->physical_page_number * B_PAGE_SIZE
+ address % B_PAGE_SIZE;
info->page = page;
return B_OK;
}
void
vm_unwire_page(VMPageWiringInfo* info)
{
VMArea* area = info->range.area;
AddressSpaceReadLocker addressSpaceLocker(area->address_space, false);
VMCache* cache = vm_area_get_locked_cache(area);
VMCacheChainLocker cacheChainLocker(cache);
if (info->page->Cache() != cache) {
cacheChainLocker.LockAllSourceCaches();
}
decrement_page_wired_count(info->page);
area->Unwire(&info->range);
cacheChainLocker.Unlock();
}
status_t
lock_memory_etc(team_id team, void* address, size_t numBytes, uint32 flags)
{
addr_t lockBaseAddress = ROUNDDOWN((addr_t)address, B_PAGE_SIZE);
addr_t lockEndAddress = ROUNDUP((addr_t)address + numBytes, B_PAGE_SIZE);
bool isUser = IS_USER_ADDRESS(address);
bool writable = (flags & B_READ_DEVICE) == 0;
uint32 requiredProtection = PAGE_PRESENT
| B_KERNEL_READ_AREA | (isUser ? B_READ_AREA : 0);
if (writable)
requiredProtection |= B_KERNEL_WRITE_AREA | (isUser ? B_WRITE_AREA : 0);
uint32 mallocFlags = isUser
? 0 : HEAP_DONT_WAIT_FOR_MEMORY | HEAP_DONT_LOCK_KERNEL_SPACE;
VMAddressSpace* addressSpace = NULL;
if (isUser) {
if (team == B_CURRENT_TEAM)
addressSpace = VMAddressSpace::GetCurrent();
else
addressSpace = VMAddressSpace::Get(team);
} else
addressSpace = VMAddressSpace::GetKernel();
if (addressSpace == NULL)
return B_ERROR;
AddressSpaceReadLocker addressSpaceLocker(addressSpace, true);
VMTranslationMap* map = addressSpace->TranslationMap();
status_t error = B_OK;
addr_t nextAddress = lockBaseAddress;
while (nextAddress != lockEndAddress) {
VMArea* area = addressSpace->LookupArea(nextAddress);
if (area == NULL) {
error = B_BAD_ADDRESS;
break;
}
addr_t areaStart = nextAddress;
addr_t areaEnd = std::min(lockEndAddress, area->Base() + area->Size());
VMAreaWiredRange* range = new(malloc_flags(mallocFlags))
VMAreaWiredRange(areaStart, areaEnd - areaStart, writable, true);
if (range == NULL) {
error = B_NO_MEMORY;
break;
}
VMCacheChainLocker cacheChainLocker(vm_area_get_locked_cache(area));
area->Wire(range);
if (area->cache_type == CACHE_TYPE_NULL
|| area->cache_type == CACHE_TYPE_DEVICE
|| area->wiring == B_FULL_LOCK
|| area->wiring == B_CONTIGUOUS) {
nextAddress = areaEnd;
continue;
}
cacheChainLocker.LockAllSourceCaches();
map->Lock();
for (; nextAddress != areaEnd; nextAddress += B_PAGE_SIZE) {
phys_addr_t physicalAddress;
uint32 flags;
vm_page* page;
if (map->Query(nextAddress, &physicalAddress, &flags) == B_OK
&& (flags & requiredProtection) == requiredProtection
&& (page = vm_lookup_page(physicalAddress / B_PAGE_SIZE))
!= NULL) {
increment_page_wired_count(page);
} else {
map->Unlock();
cacheChainLocker.Unlock();
addressSpaceLocker.Unlock();
error = vm_soft_fault(addressSpace, nextAddress, writable,
false, isUser, &page);
addressSpaceLocker.Lock();
cacheChainLocker.SetTo(vm_area_get_locked_cache(area));
cacheChainLocker.LockAllSourceCaches();
map->Lock();
}
if (error != B_OK)
break;
}
map->Unlock();
if (error == B_OK) {
cacheChainLocker.Unlock();
} else {
if (nextAddress == areaStart) {
area->Unwire(range);
cacheChainLocker.Unlock();
range->~VMAreaWiredRange();
free_etc(range, mallocFlags);
} else
cacheChainLocker.Unlock();
break;
}
}
if (error != B_OK) {
addressSpaceLocker.Unlock();
unlock_memory_etc(team, (void*)lockBaseAddress,
nextAddress - lockBaseAddress, flags);
}
return error;
}
status_t
lock_memory(void* address, size_t numBytes, uint32 flags)
{
return lock_memory_etc(B_CURRENT_TEAM, address, numBytes, flags);
}
status_t
unlock_memory_etc(team_id team, void* address, size_t numBytes, uint32 flags)
{
addr_t lockBaseAddress = ROUNDDOWN((addr_t)address, B_PAGE_SIZE);
addr_t lockEndAddress = ROUNDUP((addr_t)address + numBytes, B_PAGE_SIZE);
bool isUser = IS_USER_ADDRESS(address);
bool writable = (flags & B_READ_DEVICE) == 0;
uint32 requiredProtection = PAGE_PRESENT
| B_KERNEL_READ_AREA | (isUser ? B_READ_AREA : 0);
if (writable)
requiredProtection |= B_KERNEL_WRITE_AREA | (isUser ? B_WRITE_AREA : 0);
uint32 mallocFlags = isUser
? 0 : HEAP_DONT_WAIT_FOR_MEMORY | HEAP_DONT_LOCK_KERNEL_SPACE;
VMAddressSpace* addressSpace = NULL;
if (isUser) {
if (team == B_CURRENT_TEAM)
addressSpace = VMAddressSpace::GetCurrent();
else
addressSpace = VMAddressSpace::Get(team);
} else
addressSpace = VMAddressSpace::GetKernel();
if (addressSpace == NULL)
return B_ERROR;
AddressSpaceReadLocker addressSpaceLocker(addressSpace, false);
VMTranslationMap* map = addressSpace->TranslationMap();
status_t error = B_OK;
addr_t nextAddress = lockBaseAddress;
while (nextAddress != lockEndAddress) {
VMArea* area = addressSpace->LookupArea(nextAddress);
if (area == NULL) {
error = B_BAD_ADDRESS;
break;
}
addr_t areaStart = nextAddress;
addr_t areaEnd = std::min(lockEndAddress, area->Base() + area->Size());
VMCacheChainLocker cacheChainLocker(vm_area_get_locked_cache(area));
if (area->cache_type == CACHE_TYPE_NULL
|| area->cache_type == CACHE_TYPE_DEVICE
|| area->wiring == B_FULL_LOCK
|| area->wiring == B_CONTIGUOUS) {
nextAddress = areaEnd;
VMAreaWiredRange* range = area->Unwire(areaStart,
areaEnd - areaStart, writable);
cacheChainLocker.Unlock();
if (range != NULL) {
range->~VMAreaWiredRange();
free_etc(range, mallocFlags);
}
continue;
}
cacheChainLocker.LockAllSourceCaches();
map->Lock();
for (; nextAddress != areaEnd; nextAddress += B_PAGE_SIZE) {
phys_addr_t physicalAddress;
uint32 flags;
vm_page* page;
if (map->Query(nextAddress, &physicalAddress, &flags) == B_OK
&& (flags & PAGE_PRESENT) != 0
&& (page = vm_lookup_page(physicalAddress / B_PAGE_SIZE))
!= NULL) {
decrement_page_wired_count(page);
} else {
panic("unlock_memory_etc(): Failed to unwire page: address "
"space %p, address: %#" B_PRIxADDR, addressSpace,
nextAddress);
error = B_BAD_VALUE;
break;
}
}
map->Unlock();
VMAreaWiredRange* range = area->Unwire(areaStart,
areaEnd - areaStart, writable);
cacheChainLocker.Unlock();
if (range != NULL) {
range->~VMAreaWiredRange();
free_etc(range, mallocFlags);
}
if (error != B_OK)
break;
}
addressSpace->Put();
return error;
}
status_t
unlock_memory(void* address, size_t numBytes, uint32 flags)
{
return unlock_memory_etc(B_CURRENT_TEAM, address, numBytes, flags);
}
status_t
get_memory_map_etc(team_id team, const void* address, size_t numBytes,
physical_entry* table, uint32* _numEntries)
{
uint32 numEntries = *_numEntries;
*_numEntries = 0;
addr_t virtualAddress = (addr_t)address;
addr_t pageOffset = virtualAddress & (B_PAGE_SIZE - 1);
status_t status = B_OK;
int32 index = -1;
addr_t offset = 0;
bool interrupts = are_interrupts_enabled();
TRACE(("get_memory_map_etc(%" B_PRId32 ", %p, %lu bytes, %" B_PRIu32 " "
"entries)\n", team, address, numBytes, numEntries));
if (numEntries == 0 || numBytes == 0)
return B_BAD_VALUE;
VMAddressSpace* addressSpace;
if (IS_USER_ADDRESS(virtualAddress)) {
if (team == B_CURRENT_TEAM)
addressSpace = VMAddressSpace::GetCurrent();
else
addressSpace = VMAddressSpace::Get(team);
} else
addressSpace = VMAddressSpace::GetKernel();
if (addressSpace == NULL)
return B_ERROR;
VMAddressSpacePutter addressSpacePutter(addressSpace);
VMTranslationMap* map = addressSpace->TranslationMap();
if (interrupts)
map->Lock();
while (offset < numBytes) {
addr_t bytes = min_c(numBytes - offset, B_PAGE_SIZE);
uint32 flags;
phys_addr_t physicalAddress;
if (interrupts) {
status = map->Query((addr_t)address + offset, &physicalAddress,
&flags);
} else {
status = map->QueryInterrupt((addr_t)address + offset,
&physicalAddress, &flags);
}
if (status < B_OK)
break;
if ((flags & PAGE_PRESENT) == 0) {
panic("get_memory_map() called on unmapped memory!");
return B_BAD_ADDRESS;
}
if (index < 0 && pageOffset > 0) {
physicalAddress += pageOffset;
if (bytes > B_PAGE_SIZE - pageOffset)
bytes = B_PAGE_SIZE - pageOffset;
}
if (index < 0 || table[index].address
!= physicalAddress - table[index].size) {
if ((uint32)++index + 1 > numEntries) {
break;
}
table[index].address = physicalAddress;
table[index].size = bytes;
} else {
table[index].size += bytes;
}
offset += bytes;
}
if (interrupts)
map->Unlock();
if (status != B_OK)
return status;
if ((uint32)index + 1 > numEntries) {
*_numEntries = index;
return B_BUFFER_OVERFLOW;
}
*_numEntries = index + 1;
return B_OK;
}
extern "C" int32
__get_memory_map_haiku(const void* address, size_t numBytes,
physical_entry* table, int32 numEntries)
{
uint32 entriesRead = numEntries;
status_t error = get_memory_map_etc(B_CURRENT_TEAM, address, numBytes,
table, &entriesRead);
if (error != B_OK)
return error;
if (numEntries == 1)
return B_OK;
if (entriesRead + 1 > (uint32)numEntries)
return B_BUFFER_OVERFLOW;
table[entriesRead].address = 0;
table[entriesRead].size = 0;
return B_OK;
}
area_id
area_for(void* address)
{
return vm_area_for((addr_t)address, true);
}
area_id
find_area(const char* name)
{
return VMAreas::Find(name);
}
status_t
_get_area_info(area_id id, area_info* info, size_t size)
{
if (size != sizeof(area_info) || info == NULL)
return B_BAD_VALUE;
AddressSpaceReadLocker locker;
VMArea* area;
status_t status = locker.SetFromArea(id, area);
if (status != B_OK)
return status;
fill_area_info(area, info, size);
return B_OK;
}
status_t
_get_next_area_info(team_id team, ssize_t* cookie, area_info* info, size_t size)
{
addr_t nextBase = *(addr_t*)cookie;
if (nextBase == (addr_t)-1)
return B_ENTRY_NOT_FOUND;
if (team == B_CURRENT_TEAM)
team = team_get_current_team_id();
AddressSpaceReadLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
VMArea* area = locker.AddressSpace()->FindClosestArea(nextBase, false);
if (area == NULL) {
nextBase = (addr_t)-1;
return B_ENTRY_NOT_FOUND;
}
fill_area_info(area, info, size);
*cookie = (ssize_t)(area->Base() + 1);
return B_OK;
}
status_t
set_area_protection(area_id area, uint32 newProtection)
{
return vm_set_area_protection(area, newProtection, true);
}
status_t
resize_area(area_id areaID, size_t newSize)
{
return vm_resize_area(areaID, newSize, true);
}
area_id
transfer_area(area_id id, void** _address, uint32 addressSpec, team_id target,
bool kernel)
{
area_info info;
status_t status = get_area_info(id, &info);
if (status != B_OK)
return status;
if (!kernel && (info.team != team_get_current_team_id()
|| (info.protection & B_KERNEL_AREA) != 0)) {
return B_NOT_ALLOWED;
}
if (!kernel) {
status = vm_set_area_protection(id,
info.protection | B_CLONEABLE_AREA, kernel);
if (status != B_OK)
return status;
}
area_id clonedArea = vm_clone_area(target, info.name, _address,
addressSpec, info.protection, REGION_NO_PRIVATE_MAP, id, kernel);
if (clonedArea < 0)
return clonedArea;
status = vm_delete_area(info.team, id, kernel);
if (status != B_OK) {
vm_delete_area(target, clonedArea, kernel);
return status;
}
vm_set_area_protection(clonedArea, info.protection, kernel);
return clonedArea;
}
extern "C" area_id
__map_physical_memory_haiku(const char* name, phys_addr_t physicalAddress,
size_t numBytes, uint32 addressSpec, uint32 protection,
void** _virtualAddress)
{
return vm_map_physical_memory(VMAddressSpace::KernelID(), name,
_virtualAddress, addressSpec, numBytes, protection, physicalAddress,
false);
}
area_id
clone_area(const char* name, void** _address, uint32 addressSpec,
uint32 protection, area_id source)
{
if ((protection & B_KERNEL_PROTECTION) == 0)
protection |= B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA;
return vm_clone_area(VMAddressSpace::KernelID(), name, _address,
addressSpec, protection, REGION_NO_PRIVATE_MAP, source, true);
}
area_id
create_area_etc(team_id team, const char* name, size_t size, uint32 lock,
uint32 protection, uint32 flags, uint32 guardSize,
const virtual_address_restrictions* virtualAddressRestrictions,
const physical_address_restrictions* physicalAddressRestrictions,
void** _address)
{
return vm_create_anonymous_area(team, name, size, lock, protection, flags,
guardSize, virtualAddressRestrictions, physicalAddressRestrictions,
true, _address);
}
extern "C" area_id
__create_area_haiku(const char* name, void** _address, uint32 addressSpec,
size_t size, uint32 lock, uint32 protection)
{
virtual_address_restrictions virtualRestrictions = {};
virtualRestrictions.address = *_address;
virtualRestrictions.address_specification = addressSpec;
physical_address_restrictions physicalRestrictions = {};
return vm_create_anonymous_area(VMAddressSpace::KernelID(), name, size,
lock, protection, 0, 0, &virtualRestrictions, &physicalRestrictions,
true, _address);
}
status_t
delete_area(area_id area)
{
return vm_delete_area(VMAddressSpace::KernelID(), area, true);
}
status_t
_user_reserve_address_range(addr_t* userAddress, uint32 addressSpec,
addr_t size)
{
switch (addressSpec) {
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
return B_BAD_VALUE;
}
addr_t address;
if (!IS_USER_ADDRESS(userAddress)
|| user_memcpy(&address, userAddress, sizeof(address)) != B_OK)
return B_BAD_ADDRESS;
status_t status = vm_reserve_address_range(
VMAddressSpace::CurrentID(), (void**)&address, addressSpec, size,
RESERVED_AVOID_BASE);
if (status != B_OK)
return status;
if (user_memcpy(userAddress, &address, sizeof(address)) != B_OK) {
vm_unreserve_address_range(VMAddressSpace::CurrentID(),
(void*)address, size);
return B_BAD_ADDRESS;
}
return B_OK;
}
status_t
_user_unreserve_address_range(addr_t address, addr_t size)
{
return vm_unreserve_address_range(VMAddressSpace::CurrentID(),
(void*)address, size);
}
area_id
_user_area_for(void* address)
{
return vm_area_for((addr_t)address, false);
}
area_id
_user_find_area(const char* userName)
{
char name[B_OS_NAME_LENGTH];
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_OS_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return find_area(name);
}
status_t
_user_get_area_info(area_id area, area_info* userInfo)
{
if (!IS_USER_ADDRESS(userInfo))
return B_BAD_ADDRESS;
area_info info;
status_t status = get_area_info(area, &info);
if (status < B_OK)
return status;
if (geteuid() != 0) {
if (info.team != team_get_current_team_id()) {
if (team_geteuid(info.team) != geteuid())
return B_NOT_ALLOWED;
}
info.protection &= B_USER_AREA_FLAGS;
}
if (user_memcpy(userInfo, &info, sizeof(area_info)) < B_OK)
return B_BAD_ADDRESS;
return status;
}
status_t
_user_get_next_area_info(team_id team, ssize_t* userCookie, area_info* userInfo)
{
ssize_t cookie;
if (!IS_USER_ADDRESS(userCookie)
|| !IS_USER_ADDRESS(userInfo)
|| user_memcpy(&cookie, userCookie, sizeof(ssize_t)) < B_OK)
return B_BAD_ADDRESS;
area_info info;
status_t status = _get_next_area_info(team, &cookie, &info,
sizeof(area_info));
if (status != B_OK)
return status;
if (geteuid() != 0) {
if (info.team != team_get_current_team_id()) {
if (team_geteuid(info.team) != geteuid())
return B_NOT_ALLOWED;
}
info.protection &= B_USER_AREA_FLAGS;
}
if (user_memcpy(userCookie, &cookie, sizeof(ssize_t)) < B_OK
|| user_memcpy(userInfo, &info, sizeof(area_info)) < B_OK)
return B_BAD_ADDRESS;
return status;
}
status_t
_user_set_area_protection(area_id area, uint32 newProtection)
{
if ((newProtection & ~(B_USER_PROTECTION | B_CLONEABLE_AREA)) != 0)
return B_BAD_VALUE;
return vm_set_area_protection(area, newProtection, false);
}
status_t
_user_resize_area(area_id area, size_t newSize)
{
return vm_resize_area(area, newSize, false);
}
area_id
_user_transfer_area(area_id area, void** userAddress, uint32 addressSpec,
team_id target)
{
switch (addressSpec) {
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
return B_BAD_VALUE;
}
void* address;
if (!IS_USER_ADDRESS(userAddress)
|| user_memcpy(&address, userAddress, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
area_id newArea = transfer_area(area, &address, addressSpec, target, false);
if (newArea < B_OK)
return newArea;
if (user_memcpy(userAddress, &address, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
return newArea;
}
area_id
_user_clone_area(const char* userName, void** userAddress, uint32 addressSpec,
uint32 protection, area_id sourceArea)
{
char name[B_OS_NAME_LENGTH];
void* address;
switch (addressSpec) {
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
return B_BAD_VALUE;
}
if ((protection & ~B_USER_AREA_FLAGS) != 0)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userName)
|| !IS_USER_ADDRESS(userAddress)
|| user_strlcpy(name, userName, sizeof(name)) < B_OK
|| user_memcpy(&address, userAddress, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
area_id clonedArea = vm_clone_area(VMAddressSpace::CurrentID(), name,
&address, addressSpec, protection, REGION_NO_PRIVATE_MAP, sourceArea,
false);
if (clonedArea < B_OK)
return clonedArea;
if (user_memcpy(userAddress, &address, sizeof(address)) < B_OK) {
delete_area(clonedArea);
return B_BAD_ADDRESS;
}
return clonedArea;
}
area_id
_user_create_area(const char* userName, void** userAddress, uint32 addressSpec,
size_t size, uint32 lock, uint32 protection)
{
char name[B_OS_NAME_LENGTH];
void* address;
switch (addressSpec) {
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
return B_BAD_VALUE;
}
if ((protection & ~B_USER_AREA_FLAGS) != 0)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userName)
|| !IS_USER_ADDRESS(userAddress)
|| user_strlcpy(name, userName, sizeof(name)) < B_OK
|| user_memcpy(&address, userAddress, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
if (addressSpec == B_EXACT_ADDRESS && IS_KERNEL_ADDRESS(address))
return B_BAD_VALUE;
virtual_address_restrictions virtualRestrictions = {};
virtualRestrictions.address = address;
virtualRestrictions.address_specification = addressSpec;
physical_address_restrictions physicalRestrictions = {};
area_id area = vm_create_anonymous_area(VMAddressSpace::CurrentID(), name,
size, lock, protection, 0, 0, &virtualRestrictions,
&physicalRestrictions, false, &address);
if (area >= B_OK
&& user_memcpy(userAddress, &address, sizeof(address)) < B_OK) {
delete_area(area);
return B_BAD_ADDRESS;
}
return area;
}
status_t
_user_delete_area(area_id area)
{
return vm_delete_area(VMAddressSpace::CurrentID(), area, false);
}
area_id
_user_map_file(const char* userName, void** userAddress, uint32 addressSpec,
size_t size, uint32 protection, uint32 mapping, bool unmapAddressRange,
int fd, off_t offset)
{
char name[B_OS_NAME_LENGTH];
void* address;
area_id area;
if ((protection & ~B_USER_AREA_FLAGS) != 0)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userName) || !IS_USER_ADDRESS(userAddress)
|| user_strlcpy(name, userName, B_OS_NAME_LENGTH) < B_OK
|| user_memcpy(&address, userAddress, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
if (addressSpec == B_EXACT_ADDRESS) {
if ((addr_t)address + size < (addr_t)address
|| (addr_t)address % B_PAGE_SIZE != 0) {
return B_BAD_VALUE;
}
if (!IS_USER_ADDRESS(address)
|| !IS_USER_ADDRESS((addr_t)address + size - 1)) {
return B_BAD_ADDRESS;
}
}
area = _vm_map_file(VMAddressSpace::CurrentID(), name, &address,
addressSpec, size, protection, mapping, unmapAddressRange, fd, offset,
false);
if (area < B_OK)
return area;
if (user_memcpy(userAddress, &address, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
return area;
}
status_t
_user_unmap_memory(void* _address, size_t size)
{
addr_t address = (addr_t)_address;
if (size == 0 || (addr_t)address + size < (addr_t)address
|| (addr_t)address % B_PAGE_SIZE != 0) {
return B_BAD_VALUE;
}
if (!IS_USER_ADDRESS(address)
|| !IS_USER_ADDRESS((addr_t)address + size - 1)) {
return B_BAD_ADDRESS;
}
AddressSpaceWriteLocker locker;
do {
status_t status = locker.SetTo(team_get_current_team_id());
if (status != B_OK)
return status;
} while (wait_if_address_range_is_wired(locker.AddressSpace(), address,
size, &locker));
return unmap_address_range(locker.AddressSpace(), address, size, false);
}
status_t
_user_set_memory_protection(void* _address, size_t size, uint32 protection)
{
addr_t address = (addr_t)_address;
size = PAGE_ALIGN(size);
if ((address % B_PAGE_SIZE) != 0)
return B_BAD_VALUE;
if (!is_user_address_range(_address, size)) {
return ENOMEM;
}
if ((protection & ~(B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA)) != 0)
return B_BAD_VALUE;
team_id team = team_get_current_team_id();
status_t status = check_protection(team, &protection);
if (status != B_OK)
return status;
AddressSpaceWriteLocker locker;
bool restart;
do {
restart = false;
status_t status = locker.SetTo(team);
if (status != B_OK)
return status;
addr_t currentAddress = address;
size_t sizeLeft = size;
while (sizeLeft > 0) {
VMArea* area = locker.AddressSpace()->LookupArea(currentAddress);
if (area == NULL)
return B_NO_MEMORY;
if ((area->protection & B_KERNEL_AREA) != 0)
return B_NOT_ALLOWED;
if (area->protection_max != 0
&& (protection & area->protection_max) != (protection & B_USER_PROTECTION)) {
return B_NOT_ALLOWED;
}
addr_t offset = currentAddress - area->Base();
size_t rangeSize = min_c(area->Size() - offset, sizeLeft);
AreaCacheLocker cacheLocker(area);
if (wait_if_area_range_is_wired(area, currentAddress, rangeSize,
&locker, &cacheLocker)) {
restart = true;
break;
}
cacheLocker.Unlock();
currentAddress += rangeSize;
sizeLeft -= rangeSize;
}
} while (restart);
VMTranslationMap* map = locker.AddressSpace()->TranslationMap();
addr_t currentAddress = address;
size_t sizeLeft = size;
while (sizeLeft > 0) {
VMArea* area = locker.AddressSpace()->LookupArea(currentAddress);
if (area == NULL)
return B_NO_MEMORY;
addr_t offset = currentAddress - area->Base();
size_t rangeSize = min_c(area->Size() - offset, sizeLeft);
currentAddress += rangeSize;
sizeLeft -= rangeSize;
if (area->page_protections == NULL) {
if (area->protection == protection)
continue;
if (offset == 0 && rangeSize == area->Size()) {
status_t status = vm_set_area_protection(
area->id, protection, false);
if (status != B_OK)
return status;
continue;
}
status_t status = allocate_area_page_protections(area);
if (status != B_OK)
return status;
}
VMCache* topCache = vm_area_get_locked_cache(area);
VMCacheChainLocker cacheChainLocker(topCache);
cacheChainLocker.LockAllSourceCaches();
if (is_area_only_cache_user(area) && !topCache->CanOvercommit()) {
uint8 commitProtection = B_WRITE_AREA;
if (topCache->source == NULL)
commitProtection |= B_READ_AREA;
const bool newNeedsCommitment = (protection & commitProtection) != 0;
ssize_t commitmentChange = 0;
const off_t areaCacheBase = area->Base() - area->cache_offset;
for (addr_t pageAddress = area->Base() + offset;
pageAddress < currentAddress; pageAddress += B_PAGE_SIZE) {
const off_t cacheOffset = pageAddress - areaCacheBase;
if (topCache->LookupPage(cacheOffset) != NULL
|| topCache->StoreHasPage(cacheOffset)) {
continue;
}
const bool nowNeedsCommitment
= (get_area_page_protection(area, pageAddress) & commitProtection) != 0;
if (newNeedsCommitment && !nowNeedsCommitment)
commitmentChange += B_PAGE_SIZE;
else if (!newNeedsCommitment && nowNeedsCommitment)
commitmentChange -= B_PAGE_SIZE;
}
if (commitmentChange != 0) {
off_t newCommitment = topCache->Commitment() + commitmentChange;
const ssize_t topCacheSize = topCache->virtual_end - topCache->virtual_base;
if (newCommitment > topCacheSize) {
KDEBUG_ONLY(dprintf("set_memory_protection(area %d): new commitment "
"greater than cache size, recomputing\n", area->id));
newCommitment = (compute_area_page_commitment(area) * B_PAGE_SIZE)
+ commitmentChange;
}
status_t status = topCache->Commit(newCommitment, VM_PRIORITY_USER);
if (status != B_OK)
return status;
}
}
for (addr_t pageAddress = area->Base() + offset;
pageAddress < currentAddress; pageAddress += B_PAGE_SIZE) {
map->Lock();
set_area_page_protection(area, pageAddress, protection);
phys_addr_t physicalAddress;
uint32 flags;
status_t error = map->Query(pageAddress, &physicalAddress, &flags);
if (error != B_OK || (flags & PAGE_PRESENT) == 0) {
map->Unlock();
continue;
}
vm_page* page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL) {
panic("area %p looking up page failed for pa %#" B_PRIxPHYSADDR
"\n", area, physicalAddress);
map->Unlock();
return B_ERROR;
}
bool unmapPage = page->Cache() != topCache
&& (protection & B_WRITE_AREA) != 0;
if (!unmapPage)
map->ProtectPage(area, pageAddress, protection);
map->Unlock();
if (unmapPage) {
DEBUG_PAGE_ACCESS_START(page);
unmap_page(area, pageAddress);
DEBUG_PAGE_ACCESS_END(page);
}
}
}
return B_OK;
}
status_t
_user_sync_memory(void* _address, size_t size, uint32 flags)
{
addr_t address = (addr_t)_address;
size = PAGE_ALIGN(size);
if ((address % B_PAGE_SIZE) != 0)
return B_BAD_VALUE;
if (!is_user_address_range(_address, size)) {
return ENOMEM;
}
bool writeSync = (flags & MS_SYNC) != 0;
bool writeAsync = (flags & MS_ASYNC) != 0;
if (writeSync && writeAsync)
return B_BAD_VALUE;
if (size == 0 || (!writeSync && !writeAsync))
return B_OK;
while (size > 0) {
AddressSpaceReadLocker locker;
status_t error = locker.SetTo(team_get_current_team_id());
if (error != B_OK)
return error;
VMArea* area = locker.AddressSpace()->LookupArea(address);
if (area == NULL)
return B_NO_MEMORY;
uint32 offset = address - area->Base();
size_t rangeSize = min_c(area->Size() - offset, size);
offset += area->cache_offset;
AreaCacheLocker cacheLocker(area);
if (!cacheLocker)
return B_BAD_VALUE;
VMCache* cache = area->cache;
locker.Unlock();
uint32 firstPage = offset >> PAGE_SHIFT;
uint32 endPage = firstPage + (rangeSize >> PAGE_SHIFT);
if (cache->type == CACHE_TYPE_VNODE) {
if (writeSync) {
error = vm_page_write_modified_page_range(cache, firstPage,
endPage);
if (error != B_OK)
return error;
} else {
vm_page_schedule_write_page_range(cache, firstPage, endPage);
}
}
address += rangeSize;
size -= rangeSize;
}
return B_OK;
}
status_t
_user_memory_advice(void* _address, size_t size, uint32 advice)
{
addr_t address = (addr_t)_address;
if ((address % B_PAGE_SIZE) != 0)
return B_BAD_VALUE;
size = PAGE_ALIGN(size);
if (!is_user_address_range(_address, size)) {
return B_NO_MEMORY;
}
switch (advice) {
case MADV_NORMAL:
case MADV_SEQUENTIAL:
case MADV_RANDOM:
case MADV_WILLNEED:
case MADV_DONTNEED:
break;
case MADV_FREE:
{
AddressSpaceWriteLocker locker;
do {
status_t status = locker.SetTo(team_get_current_team_id());
if (status != B_OK)
return status;
} while (wait_if_address_range_is_wired(locker.AddressSpace(),
address, size, &locker));
discard_address_range(locker.AddressSpace(), address, size, false);
break;
}
default:
return B_BAD_VALUE;
}
return B_OK;
}
status_t
_user_get_memory_properties(team_id teamID, const void* address,
uint32* _protected, uint32* _lock)
{
if (!IS_USER_ADDRESS(_protected) || !IS_USER_ADDRESS(_lock))
return B_BAD_ADDRESS;
if (teamID != B_CURRENT_TEAM && teamID != team_get_current_team_id()
&& geteuid() != 0)
return B_NOT_ALLOWED;
AddressSpaceReadLocker locker;
status_t error = locker.SetTo(teamID);
if (error != B_OK)
return error;
VMArea* area = locker.AddressSpace()->LookupArea((addr_t)address);
if (area == NULL)
return B_NO_MEMORY;
uint32 protection = get_area_page_protection(area, (addr_t)address);
uint32 wiring = area->wiring;
locker.Unlock();
error = user_memcpy(_protected, &protection, sizeof(protection));
if (error != B_OK)
return error;
error = user_memcpy(_lock, &wiring, sizeof(wiring));
return error;
}
static status_t
user_set_memory_swappable(const void* _address, size_t size, bool swappable)
{
#if ENABLE_SWAP_SUPPORT
addr_t address = (addr_t)_address;
size = PAGE_ALIGN(size);
if ((address % B_PAGE_SIZE) != 0)
return EINVAL;
if (!is_user_address_range(_address, size))
return EINVAL;
const addr_t endAddress = address + size;
status_t error = lock_memory_etc(B_CURRENT_TEAM,
(void*)address, endAddress - address, 0);
if (error != B_OK)
return error;
AddressSpaceReadLocker addressSpaceLocker;
error = addressSpaceLocker.SetTo(team_get_current_team_id());
if (error != B_OK)
return error;
VMAddressSpace* addressSpace = addressSpaceLocker.AddressSpace();
addr_t nextAddress = address;
while (nextAddress != endAddress) {
VMArea* area = addressSpace->LookupArea(nextAddress);
if (area == NULL) {
error = B_BAD_ADDRESS;
break;
}
const addr_t areaStart = nextAddress;
const addr_t areaEnd = std::min(endAddress, area->Base() + area->Size());
nextAddress = areaEnd;
VMCacheChainLocker cacheChainLocker(vm_area_get_locked_cache(area));
VMAnonymousCache* anonCache = NULL;
if (dynamic_cast<VMAnonymousNoSwapCache*>(area->cache) != NULL) {
} else if ((anonCache = dynamic_cast<VMAnonymousCache*>(area->cache)) != NULL) {
error = anonCache->SetCanSwapPages(areaStart - area->Base(),
areaEnd - areaStart, swappable);
} else {
error = EINVAL;
}
cacheChainLocker.Unlock();
if (error != B_OK)
break;
}
addressSpaceLocker.Unlock();
unlock_memory_etc(B_CURRENT_TEAM,
(void*)address, endAddress - address, 0);
return error;
#else
return B_OK;
#endif
}
status_t
_user_mlock(const void* _address, size_t size)
{
return user_set_memory_swappable(_address, size, false);
}
status_t
_user_munlock(const void* _address, size_t size)
{
return user_set_memory_swappable(_address, size, true);
}
#if defined(__i386__) && B_HAIKU_PHYSICAL_BITS > 32
struct physical_entry_beos {
uint32 address;
uint32 size;
};
extern "C" int32
__get_memory_map_beos(const void* _address, size_t numBytes,
physical_entry_beos* table, int32 numEntries)
{
if (numEntries <= 0)
return B_BAD_VALUE;
const uint8* address = (const uint8*)_address;
int32 count = 0;
while (numBytes > 0 && count < numEntries) {
physical_entry entry;
status_t result = __get_memory_map_haiku(address, numBytes, &entry, 1);
if (result < 0) {
if (result != B_BUFFER_OVERFLOW)
return result;
}
if (entry.address >= (phys_addr_t)1 << 32) {
panic("get_memory_map(): Address is greater 4 GB!");
return B_ERROR;
}
table[count].address = entry.address;
table[count++].size = entry.size;
address += entry.size;
numBytes -= entry.size;
}
if (count < numEntries) {
table[count].address = 0;
table[count].size = 0;
}
return B_OK;
}
extern "C" area_id
__map_physical_memory_beos(const char* name, void* physicalAddress,
size_t numBytes, uint32 addressSpec, uint32 protection,
void** _virtualAddress)
{
return __map_physical_memory_haiku(name, (addr_t)physicalAddress, numBytes,
addressSpec, protection, _virtualAddress);
}
extern "C" area_id
__create_area_beos(const char* name, void** _address, uint32 addressSpec,
size_t size, uint32 lock, uint32 protection)
{
switch (lock) {
case B_NO_LOCK:
break;
case B_FULL_LOCK:
case B_LAZY_LOCK:
lock = B_32_BIT_FULL_LOCK;
break;
case B_CONTIGUOUS:
lock = B_32_BIT_CONTIGUOUS;
break;
}
return __create_area_haiku(name, _address, addressSpec, size, lock,
protection);
}
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__get_memory_map_beos", "get_memory_map@",
"BASE");
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__map_physical_memory_beos",
"map_physical_memory@", "BASE");
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__create_area_beos", "create_area@",
"BASE");
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__get_memory_map_haiku",
"get_memory_map@@", "1_ALPHA3");
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__map_physical_memory_haiku",
"map_physical_memory@@", "1_ALPHA3");
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__create_area_haiku", "create_area@@",
"1_ALPHA3");
#else
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__get_memory_map_haiku",
"get_memory_map@@", "BASE");
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__map_physical_memory_haiku",
"map_physical_memory@@", "BASE");
DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__create_area_haiku", "create_area@@",
"BASE");
#endif