Files
haiku-beta6/src/system/kernel/vm/vm.cpp
T
Axel Dörfler 3d268eda3d * Extracted file_map API out of the file cache - it's now an optional service
that can be used by file systems.
* Changed the way the file cache works: instead of reading/writing to the
  underlying device directly, it can now be used for any data source, ie.
  also network file systems.
* As a result, the former pages_io() moved to the VFS layer, and can now be
  called by a file system via {read|write}_file_io_vec_pages() (naming
  suggestions are always welcomed :-)). It now gets an FD, and uses that to
  communicate with the device (via its fs_{read|write}_pages() hooks).
* The file_cache_{read|write}() functions must now be called without holding
  an I/O relevant file system lock. That allows the file cache to prepare the
  pages without colliding with the page writer, IOW the "mayBlock" flag can
  go into the attic again (yay!).
* This also results in a much better performance when the system does I/O and
  is low on memory, as the page writer can now finally write back some pages,
  and that even without maxing out the CPU :)
* The API changes put slightly more burden on the fs_{read|write}_pages()
  hooks, but in combination with the file_map it's still pretty straight
  forward. It just will have to dispatch the call to the underlying device
  directly, usually it will just call its fs_{read|write}_pages() hooks
  via the above mentioned calls.
* Ported BFS and FAT to the new API, the latter has not been tested, though.
* Also ported the API changes to the fs_shell. I also completely removed its
  file cache level page handling - the downside is that device access is no
  longer cached (ie. depends on the host OS now), the upside is that the code
  is greatly simplified.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22886 a95241bf-73f2-0310-859d-f6bbb57e9c96
2007-11-10 21:19:52 +00:00

5410 lines
136 KiB
C++

/*
* Copyright 2002-2007, Axel Dörfler, [email protected].
* Distributed under the terms of the MIT License.
*
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include <vm.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <OS.h>
#include <KernelExport.h>
#include <AutoDeleter.h>
#include <vm_address_space.h>
#include <vm_priv.h>
#include <vm_page.h>
#include <vm_cache.h>
#include <vm_low_memory.h>
#include <file_cache.h>
#include <memheap.h>
#include <condition_variable.h>
#include <debug.h>
#include <console.h>
#include <int.h>
#include <smp.h>
#include <lock.h>
#include <thread.h>
#include <team.h>
#include <util/AutoLock.h>
#include <util/khash.h>
#include <boot/stage2.h>
#include <boot/elf.h>
#include <arch/cpu.h>
#include <arch/vm.h>
#include "vm_store_anonymous_noswap.h"
#include "vm_store_device.h"
#include "vm_store_null.h"
//#define TRACE_VM
//#define TRACE_FAULTS
#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
#define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
#define ROUNDOWN(a, b) (((a) / (b)) * (b))
class AddressSpaceReadLocker {
public:
AddressSpaceReadLocker(team_id team);
AddressSpaceReadLocker(vm_address_space* space);
AddressSpaceReadLocker();
~AddressSpaceReadLocker();
status_t SetTo(team_id team);
void SetTo(vm_address_space* space);
status_t SetFromArea(area_id areaID, vm_area*& area);
bool IsLocked() const { return fLocked; }
void Unlock();
void Unset();
vm_address_space* AddressSpace() { return fSpace; }
private:
vm_address_space* fSpace;
bool fLocked;
};
class AddressSpaceWriteLocker {
public:
AddressSpaceWriteLocker(team_id team);
AddressSpaceWriteLocker();
~AddressSpaceWriteLocker();
status_t SetTo(team_id team);
status_t SetFromArea(area_id areaID, vm_area*& area);
status_t SetFromArea(team_id team, area_id areaID, bool allowKernel,
vm_area*& area);
status_t SetFromArea(team_id team, area_id areaID, vm_area*& area);
bool IsLocked() const { return fLocked; }
void Unlock();
void DegradeToReadLock();
void Unset();
vm_address_space* AddressSpace() { return fSpace; }
private:
vm_address_space* fSpace;
bool fLocked;
bool fDegraded;
};
class MultiAddressSpaceLocker {
public:
MultiAddressSpaceLocker();
~MultiAddressSpaceLocker();
inline status_t AddTeam(team_id team, bool writeLock,
vm_address_space** _space = NULL);
inline status_t AddArea(area_id area, bool writeLock,
vm_address_space** _space = NULL);
status_t AddAreaCacheAndLock(area_id areaID, bool writeLockThisOne,
bool writeLockOthers, vm_area*& _area, vm_cache** _cache = NULL,
bool checkNoCacheChange = false);
status_t Lock();
void Unlock();
bool IsLocked() const { return fLocked; }
void Unset();
private:
struct lock_item {
vm_address_space* space;
bool write_lock;
};
bool _ResizeIfNeeded();
int32 _IndexOfAddressSpace(vm_address_space* space) const;
status_t _AddAddressSpace(vm_address_space* space, bool writeLock,
vm_address_space** _space);
static int _CompareItems(const void* _a, const void* _b);
lock_item* fItems;
int32 fCapacity;
int32 fCount;
bool fLocked;
};
class AreaCacheLocking {
public:
inline bool Lock(vm_cache* lockable)
{
return false;
}
inline void Unlock(vm_cache* lockable)
{
vm_area_put_locked_cache(lockable);
}
};
class AreaCacheLocker : public AutoLocker<vm_cache, AreaCacheLocking> {
public:
inline AreaCacheLocker(vm_cache* cache = NULL)
: AutoLocker<vm_cache, AreaCacheLocking>(cache, true)
{
}
inline AreaCacheLocker(vm_area* area)
: AutoLocker<vm_cache, AreaCacheLocking>()
{
SetTo(area);
}
inline void SetTo(vm_area* area)
{
return AutoLocker<vm_cache, AreaCacheLocking>::SetTo(
area != NULL ? vm_area_get_locked_cache(area) : NULL, true, true);
}
};
#define REGION_HASH_TABLE_SIZE 1024
static area_id sNextAreaID;
static hash_table *sAreaHash;
static sem_id sAreaHashLock;
static mutex sMappingLock;
static mutex sAreaCacheLock;
static off_t sAvailableMemory;
static benaphore sAvailableMemoryLock;
// function declarations
static void delete_area(vm_address_space *addressSpace, vm_area *area);
static vm_address_space *get_address_space_by_area_id(area_id id);
static status_t vm_soft_fault(addr_t address, bool isWrite, bool isUser);
// #pragma mark -
AddressSpaceReadLocker::AddressSpaceReadLocker(team_id team)
:
fSpace(NULL),
fLocked(false)
{
SetTo(team);
}
//! Takes over the reference of the address space
AddressSpaceReadLocker::AddressSpaceReadLocker(vm_address_space* space)
:
fSpace(NULL),
fLocked(false)
{
SetTo(space);
}
AddressSpaceReadLocker::AddressSpaceReadLocker()
:
fSpace(NULL),
fLocked(false)
{
}
AddressSpaceReadLocker::~AddressSpaceReadLocker()
{
Unset();
}
void
AddressSpaceReadLocker::Unset()
{
Unlock();
if (fSpace != NULL)
vm_put_address_space(fSpace);
}
status_t
AddressSpaceReadLocker::SetTo(team_id team)
{
fSpace = vm_get_address_space_by_id(team);
if (fSpace == NULL)
return B_BAD_TEAM_ID;
acquire_sem_etc(fSpace->sem, READ_COUNT, 0, 0);
fLocked = true;
return B_OK;
}
//! Takes over the reference of the address space
void
AddressSpaceReadLocker::SetTo(vm_address_space* space)
{
fSpace = space;
acquire_sem_etc(fSpace->sem, READ_COUNT, 0, 0);
fLocked = true;
}
status_t
AddressSpaceReadLocker::SetFromArea(area_id areaID, vm_area*& area)
{
fSpace = get_address_space_by_area_id(areaID);
if (fSpace == NULL)
return B_BAD_TEAM_ID;
acquire_sem_etc(fSpace->sem, READ_COUNT, 0, 0);
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
area = (vm_area *)hash_lookup(sAreaHash, &areaID);
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
if (area == NULL || area->address_space != fSpace) {
release_sem_etc(fSpace->sem, READ_COUNT, 0);
return B_BAD_VALUE;
}
fLocked = true;
return B_OK;
}
void
AddressSpaceReadLocker::Unlock()
{
if (fLocked) {
release_sem_etc(fSpace->sem, READ_COUNT, 0);
fLocked = false;
}
}
// #pragma mark -
AddressSpaceWriteLocker::AddressSpaceWriteLocker(team_id team)
:
fSpace(NULL),
fLocked(false),
fDegraded(false)
{
SetTo(team);
}
AddressSpaceWriteLocker::AddressSpaceWriteLocker()
:
fSpace(NULL),
fLocked(false),
fDegraded(false)
{
}
AddressSpaceWriteLocker::~AddressSpaceWriteLocker()
{
Unset();
}
void
AddressSpaceWriteLocker::Unset()
{
Unlock();
if (fSpace != NULL)
vm_put_address_space(fSpace);
}
status_t
AddressSpaceWriteLocker::SetTo(team_id team)
{
fSpace = vm_get_address_space_by_id(team);
if (fSpace == NULL)
return B_BAD_TEAM_ID;
acquire_sem_etc(fSpace->sem, WRITE_COUNT, 0, 0);
fLocked = true;
return B_OK;
}
status_t
AddressSpaceWriteLocker::SetFromArea(area_id areaID, vm_area*& area)
{
fSpace = get_address_space_by_area_id(areaID);
if (fSpace == NULL)
return B_BAD_VALUE;
acquire_sem_etc(fSpace->sem, WRITE_COUNT, 0, 0);
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
area = (vm_area*)hash_lookup(sAreaHash, &areaID);
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
if (area == NULL || area->address_space != fSpace) {
release_sem_etc(fSpace->sem, WRITE_COUNT, 0);
return B_BAD_VALUE;
}
fLocked = true;
return B_OK;
}
status_t
AddressSpaceWriteLocker::SetFromArea(team_id team, area_id areaID,
bool allowKernel, vm_area*& area)
{
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
area = (vm_area *)hash_lookup(sAreaHash, &areaID);
if (area != NULL
&& (area->address_space->id == team
|| allowKernel && team == vm_kernel_address_space_id())) {
fSpace = area->address_space;
atomic_add(&fSpace->ref_count, 1);
}
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
if (fSpace == NULL)
return B_BAD_VALUE;
// Second try to get the area -- this time with the address space
// write lock held
acquire_sem_etc(fSpace->sem, WRITE_COUNT, 0, 0);
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
area = (vm_area *)hash_lookup(sAreaHash, &areaID);
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
if (area == NULL) {
release_sem_etc(fSpace->sem, WRITE_COUNT, 0);
return B_BAD_VALUE;
}
fLocked = true;
return B_OK;
}
status_t
AddressSpaceWriteLocker::SetFromArea(team_id team, area_id areaID,
vm_area*& area)
{
return SetFromArea(team, areaID, false, area);
}
void
AddressSpaceWriteLocker::Unlock()
{
if (fLocked) {
release_sem_etc(fSpace->sem, fDegraded ? READ_COUNT : WRITE_COUNT, 0);
fLocked = false;
fDegraded = false;
}
}
void
AddressSpaceWriteLocker::DegradeToReadLock()
{
release_sem_etc(fSpace->sem, WRITE_COUNT - READ_COUNT, 0);
fDegraded = true;
}
// #pragma mark -
MultiAddressSpaceLocker::MultiAddressSpaceLocker()
:
fItems(NULL),
fCapacity(0),
fCount(0),
fLocked(false)
{
}
MultiAddressSpaceLocker::~MultiAddressSpaceLocker()
{
Unset();
free(fItems);
}
/*static*/ int
MultiAddressSpaceLocker::_CompareItems(const void* _a, const void* _b)
{
lock_item* a = (lock_item*)_a;
lock_item* b = (lock_item*)_b;
return a->space->id - b->space->id;
}
bool
MultiAddressSpaceLocker::_ResizeIfNeeded()
{
if (fCount == fCapacity) {
lock_item* items = (lock_item*)realloc(fItems,
(fCapacity + 4) * sizeof(lock_item));
if (items == NULL)
return false;
fCapacity += 4;
fItems = items;
}
return true;
}
int32
MultiAddressSpaceLocker::_IndexOfAddressSpace(vm_address_space* space) const
{
for (int32 i = 0; i < fCount; i++) {
if (fItems[i].space == space)
return i;
}
return -1;
}
status_t
MultiAddressSpaceLocker::_AddAddressSpace(vm_address_space* space,
bool writeLock, vm_address_space** _space)
{
if (!space)
return B_BAD_VALUE;
int32 index = _IndexOfAddressSpace(space);
if (index < 0) {
if (!_ResizeIfNeeded()) {
vm_put_address_space(space);
return B_NO_MEMORY;
}
lock_item& item = fItems[fCount++];
item.space = space;
item.write_lock = writeLock;
} else {
// one reference is enough
vm_put_address_space(space);
fItems[index].write_lock |= writeLock;
}
if (_space != NULL)
*_space = space;
return B_OK;
}
inline status_t
MultiAddressSpaceLocker::AddTeam(team_id team, bool writeLock,
vm_address_space** _space)
{
return _AddAddressSpace(vm_get_address_space_by_id(team), writeLock,
_space);
}
inline status_t
MultiAddressSpaceLocker::AddArea(area_id area, bool writeLock,
vm_address_space** _space)
{
return _AddAddressSpace(get_address_space_by_area_id(area), writeLock,
_space);
}
void
MultiAddressSpaceLocker::Unset()
{
Unlock();
for (int32 i = 0; i < fCount; i++)
vm_put_address_space(fItems[i].space);
fCount = 0;
}
status_t
MultiAddressSpaceLocker::Lock()
{
ASSERT(!fLocked);
qsort(fItems, fCount, sizeof(lock_item), &_CompareItems);
for (int32 i = 0; i < fCount; i++) {
status_t status = acquire_sem_etc(fItems[i].space->sem,
fItems[i].write_lock ? WRITE_COUNT : READ_COUNT, 0, 0);
if (status < B_OK) {
while (--i >= 0) {
release_sem_etc(fItems[i].space->sem,
fItems[i].write_lock ? WRITE_COUNT : READ_COUNT, 0);
}
return status;
}
}
fLocked = true;
return B_OK;
}
void
MultiAddressSpaceLocker::Unlock()
{
if (!fLocked)
return;
for (int32 i = 0; i < fCount; i++) {
release_sem_etc(fItems[i].space->sem,
fItems[i].write_lock ? WRITE_COUNT : READ_COUNT, 0);
}
fLocked = false;
}
/*! Adds all address spaces of the areas associated with the given area's cache,
locks them, and locks the cache (including a reference to it). It retries
until the situation is stable (i.e. the neither cache nor cache's areas
changed) or an error occurs. If \c checkNoCacheChange ist \c true it does
not return until all areas' \c no_cache_change flags is clear.
*/
status_t
MultiAddressSpaceLocker::AddAreaCacheAndLock(area_id areaID,
bool writeLockThisOne, bool writeLockOthers, vm_area*& _area,
vm_cache** _cache, bool checkNoCacheChange)
{
// remember the original state
int originalCount = fCount;
lock_item* originalItems = NULL;
if (fCount > 0) {
originalItems = new(nothrow) lock_item[fCount];
if (originalItems == NULL)
return B_NO_MEMORY;
memcpy(originalItems, fItems, fCount * sizeof(lock_item));
}
ArrayDeleter<lock_item> _(originalItems);
// get the cache
vm_cache* cache;
vm_area* area;
status_t error;
{
AddressSpaceReadLocker locker;
error = locker.SetFromArea(areaID, area);
if (error != B_OK)
return error;
cache = vm_area_get_locked_cache(area);
}
while (true) {
// add all areas
vm_area* firstArea = cache->areas;
for (vm_area* current = firstArea; current;
current = current->cache_next) {
error = AddArea(current->id,
current == area ? writeLockThisOne : writeLockOthers);
if (error != B_OK) {
vm_area_put_locked_cache(cache);
return error;
}
}
// unlock the cache and attempt to lock the address spaces
vm_area_put_locked_cache(cache);
error = Lock();
if (error != B_OK)
return error;
// lock the cache again and check whether anything has changed
// check whether the area is gone in the meantime
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
area = (vm_area *)hash_lookup(sAreaHash, &areaID);
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
if (area == NULL) {
Unlock();
return B_BAD_VALUE;
}
// lock the cache
vm_cache* oldCache = cache;
cache = vm_area_get_locked_cache(area);
// If neither the area's cache has changed nor its area list we're
// done...
bool done = (cache == oldCache || firstArea == cache->areas);
// ... unless we're supposed to check the areas' "no_cache_change" flag
bool yield = false;
if (done && checkNoCacheChange) {
for (vm_area *tempArea = cache->areas; tempArea != NULL;
tempArea = tempArea->cache_next) {
if (tempArea->no_cache_change) {
done = false;
yield = true;
break;
}
}
}
// If everything looks dandy, return the values.
if (done) {
_area = area;
if (_cache != NULL)
*_cache = cache;
return B_OK;
}
// Restore the original state and try again.
// Unlock the address spaces, but keep the cache locked for the next
// iteration.
Unlock();
// Get an additional reference to the original address spaces.
for (int32 i = 0; i < originalCount; i++)
atomic_add(&originalItems[i].space->ref_count, 1);
// Release all references to the current address spaces.
for (int32 i = 0; i < fCount; i++)
vm_put_address_space(fItems[i].space);
// Copy over the original state.
fCount = originalCount;
if (originalItems != NULL)
memcpy(fItems, originalItems, fCount * sizeof(lock_item));
if (yield)
thread_yield();
}
}
// #pragma mark -
static int
area_compare(void *_area, const void *key)
{
vm_area *area = (vm_area *)_area;
const area_id *id = (const area_id *)key;
if (area->id == *id)
return 0;
return -1;
}
static uint32
area_hash(void *_area, const void *key, uint32 range)
{
vm_area *area = (vm_area *)_area;
const area_id *id = (const area_id *)key;
if (area != NULL)
return area->id % range;
return (uint32)*id % range;
}
static vm_address_space *
get_address_space_by_area_id(area_id id)
{
vm_address_space* addressSpace = NULL;
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
vm_area *area = (vm_area *)hash_lookup(sAreaHash, &id);
if (area != NULL) {
addressSpace = area->address_space;
atomic_add(&addressSpace->ref_count, 1);
}
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
return addressSpace;
}
//! You need to have the address space locked when calling this function
static vm_area *
lookup_area(vm_address_space* addressSpace, area_id id)
{
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
vm_area *area = (vm_area *)hash_lookup(sAreaHash, &id);
if (area != NULL && area->address_space != addressSpace)
area = NULL;
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
return area;
}
static vm_area *
create_reserved_area_struct(vm_address_space *addressSpace, uint32 flags)
{
vm_area *reserved = (vm_area *)malloc(sizeof(vm_area));
if (reserved == NULL)
return NULL;
memset(reserved, 0, sizeof(vm_area));
reserved->id = RESERVED_AREA_ID;
// this marks it as reserved space
reserved->protection = flags;
reserved->address_space = addressSpace;
return reserved;
}
static vm_area *
create_area_struct(vm_address_space *addressSpace, const char *name,
uint32 wiring, uint32 protection)
{
// restrict the area name to B_OS_NAME_LENGTH
size_t length = strlen(name) + 1;
if (length > B_OS_NAME_LENGTH)
length = B_OS_NAME_LENGTH;
vm_area *area = (vm_area *)malloc(sizeof(vm_area));
if (area == NULL)
return NULL;
area->name = (char *)malloc(length);
if (area->name == NULL) {
free(area);
return NULL;
}
strlcpy(area->name, name, length);
area->id = atomic_add(&sNextAreaID, 1);
area->base = 0;
area->size = 0;
area->protection = protection;
area->wiring = wiring;
area->memory_type = 0;
area->cache = NULL;
area->no_cache_change = 0;
area->cache_offset = 0;
area->address_space = addressSpace;
area->address_space_next = NULL;
area->cache_next = area->cache_prev = NULL;
area->hash_next = NULL;
new (&area->mappings) vm_area_mappings;
return area;
}
/** Finds a reserved area that covers the region spanned by \a start and
* \a size, inserts the \a area into that region and makes sure that
* there are reserved regions for the remaining parts.
*/
static status_t
find_reserved_area(vm_address_space *addressSpace, addr_t start,
addr_t size, vm_area *area)
{
vm_area *next, *last = NULL;
next = addressSpace->areas;
while (next) {
if (next->base <= start && next->base + next->size >= start + size) {
// this area covers the requested range
if (next->id != RESERVED_AREA_ID) {
// but it's not reserved space, it's a real area
return B_BAD_VALUE;
}
break;
}
last = next;
next = next->address_space_next;
}
if (next == NULL)
return B_ENTRY_NOT_FOUND;
// now we have to transfer the requested part of the reserved
// range to the new area - and remove, resize or split the old
// reserved area.
if (start == next->base) {
// the area starts at the beginning of the reserved range
if (last)
last->address_space_next = area;
else
addressSpace->areas = area;
if (size == next->size) {
// the new area fully covers the reversed range
area->address_space_next = next->address_space_next;
vm_put_address_space(addressSpace);
free(next);
} else {
// resize the reserved range behind the area
area->address_space_next = next;
next->base += size;
next->size -= size;
}
} else if (start + size == next->base + next->size) {
// the area is at the end of the reserved range
area->address_space_next = next->address_space_next;
next->address_space_next = area;
// resize the reserved range before the area
next->size = start - next->base;
} else {
// the area splits the reserved range into two separate ones
// we need a new reserved area to cover this space
vm_area *reserved = create_reserved_area_struct(addressSpace,
next->protection);
if (reserved == NULL)
return B_NO_MEMORY;
atomic_add(&addressSpace->ref_count, 1);
reserved->address_space_next = next->address_space_next;
area->address_space_next = reserved;
next->address_space_next = area;
// resize regions
reserved->size = next->base + next->size - start - size;
next->size = start - next->base;
reserved->base = start + size;
reserved->cache_offset = next->cache_offset;
}
area->base = start;
area->size = size;
addressSpace->change_count++;
return B_OK;
}
/*! Must be called with this address space's sem held */
static status_t
find_and_insert_area_slot(vm_address_space *addressSpace, addr_t start,
addr_t size, addr_t end, uint32 addressSpec, vm_area *area)
{
vm_area *last = NULL;
vm_area *next;
bool foundSpot = false;
TRACE(("find_and_insert_area_slot: address space %p, start 0x%lx, "
"size %ld, end 0x%lx, addressSpec %ld, area %p\n", addressSpace, start,
size, end, addressSpec, area));
// do some sanity checking
if (start < addressSpace->base || size == 0
|| (end - 1) > (addressSpace->base + (addressSpace->size - 1))
|| start + size > end)
return B_BAD_ADDRESS;
if (addressSpec == B_EXACT_ADDRESS) {
// search for a reserved area
status_t status = find_reserved_area(addressSpace, start, size, area);
if (status == B_OK || status == B_BAD_VALUE)
return status;
// there was no reserved area, and the slot doesn't seem to be used already
// ToDo: this could be further optimized.
}
// walk up to the spot where we should start searching
second_chance:
next = addressSpace->areas;
while (next) {
if (next->base >= start + size) {
// we have a winner
break;
}
last = next;
next = next->address_space_next;
}
// find the right spot depending on the address specification - the area
// will be inserted directly after "last" ("next" is not referenced anymore)
switch (addressSpec) {
case B_ANY_ADDRESS:
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
// find a hole big enough for a new area
if (!last) {
// see if we can build it at the beginning of the virtual map
if (!next || (next->base >= addressSpace->base + size)) {
foundSpot = true;
area->base = addressSpace->base;
break;
}
last = next;
next = next->address_space_next;
}
// keep walking
while (next) {
if (next->base >= last->base + last->size + size) {
// we found a spot (it'll be filled up below)
break;
}
last = next;
next = next->address_space_next;
}
if ((addressSpace->base + (addressSpace->size - 1))
>= (last->base + last->size + (size - 1))) {
// got a spot
foundSpot = true;
area->base = last->base + last->size;
break;
} else {
// we didn't find a free spot - if there were any reserved areas with
// the RESERVED_AVOID_BASE flag set, we can now test those for free
// space
// ToDo: it would make sense to start with the biggest of them
next = addressSpace->areas;
last = NULL;
for (last = NULL; next; next = next->address_space_next, last = next) {
// ToDo: take free space after the reserved area into account!
if (next->size == size) {
// the reserved area is entirely covered, and thus, removed
if (last)
last->address_space_next = next->address_space_next;
else
addressSpace->areas = next->address_space_next;
foundSpot = true;
area->base = next->base;
free(next);
break;
}
if (next->size >= size) {
// the new area will be placed at the end of the reserved
// area, and the reserved area will be resized to make space
foundSpot = true;
next->size -= size;
last = next;
area->base = next->base + next->size;
break;
}
}
}
break;
case B_BASE_ADDRESS:
// find a hole big enough for a new area beginning with "start"
if (!last) {
// see if we can build it at the beginning of the specified start
if (!next || (next->base >= start + size)) {
foundSpot = true;
area->base = start;
break;
}
last = next;
next = next->address_space_next;
}
// keep walking
while (next) {
if (next->base >= last->base + last->size + size) {
// we found a spot (it'll be filled up below)
break;
}
last = next;
next = next->address_space_next;
}
if ((addressSpace->base + (addressSpace->size - 1))
>= (last->base + last->size + (size - 1))) {
// got a spot
foundSpot = true;
if (last->base + last->size <= start)
area->base = start;
else
area->base = last->base + last->size;
break;
}
// we didn't find a free spot in the requested range, so we'll
// try again without any restrictions
start = addressSpace->base;
addressSpec = B_ANY_ADDRESS;
last = NULL;
goto second_chance;
case B_EXACT_ADDRESS:
// see if we can create it exactly here
if (!last) {
if (!next || (next->base >= start + size)) {
foundSpot = true;
area->base = start;
break;
}
} else {
if (next) {
if (last->base + last->size <= start && next->base >= start + size) {
foundSpot = true;
area->base = start;
break;
}
} else {
if ((last->base + (last->size - 1)) <= start - 1) {
foundSpot = true;
area->base = start;
}
}
}
break;
default:
return B_BAD_VALUE;
}
if (!foundSpot)
return addressSpec == B_EXACT_ADDRESS ? B_BAD_VALUE : B_NO_MEMORY;
area->size = size;
if (last) {
area->address_space_next = last->address_space_next;
last->address_space_next = area;
} else {
area->address_space_next = addressSpace->areas;
addressSpace->areas = area;
}
addressSpace->change_count++;
return B_OK;
}
/** This inserts the area you pass into the specified address space.
* It will also set the "_address" argument to its base address when
* the call succeeds.
* You need to hold the vm_address_space semaphore.
*/
static status_t
insert_area(vm_address_space *addressSpace, void **_address,
uint32 addressSpec, addr_t size, vm_area *area)
{
addr_t searchBase, searchEnd;
status_t status;
switch (addressSpec) {
case B_EXACT_ADDRESS:
searchBase = (addr_t)*_address;
searchEnd = (addr_t)*_address + size;
break;
case B_BASE_ADDRESS:
searchBase = (addr_t)*_address;
searchEnd = addressSpace->base + (addressSpace->size - 1);
break;
case B_ANY_ADDRESS:
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
searchBase = addressSpace->base;
searchEnd = addressSpace->base + (addressSpace->size - 1);
break;
default:
return B_BAD_VALUE;
}
status = find_and_insert_area_slot(addressSpace, searchBase, size,
searchEnd, addressSpec, area);
if (status == B_OK) {
// ToDo: do we have to do anything about B_ANY_KERNEL_ADDRESS
// vs. B_ANY_KERNEL_BLOCK_ADDRESS here?
*_address = (void *)area->base;
}
return status;
}
/*! You need to hold the lock of the cache and the write lock of the address
space when calling this function.
Note, that in case of error your cache will be temporarily unlocked.
*/
static status_t
map_backing_store(vm_address_space *addressSpace, vm_cache *cache,
void **_virtualAddress, off_t offset, addr_t size, uint32 addressSpec,
int wiring, int protection, int mapping, vm_area **_area,
const char *areaName)
{
TRACE(("map_backing_store: aspace %p, cache %p, *vaddr %p, offset 0x%Lx, size %lu, addressSpec %ld, wiring %d, protection %d, _area %p, area_name '%s'\n",
addressSpace, cache, *_virtualAddress, offset, size, addressSpec,
wiring, protection, _area, areaName));
ASSERT_LOCKED_MUTEX(&cache->lock);
vm_area *area = create_area_struct(addressSpace, areaName, wiring,
protection);
if (area == NULL)
return B_NO_MEMORY;
vm_store *store = cache->store;
status_t status;
// if this is a private map, we need to create a new cache & store object
// pair to handle the private copies of pages as they are written to
vm_cache* sourceCache = cache;
if (mapping == REGION_PRIVATE_MAP) {
vm_cache *newCache;
vm_store *newStore;
// create an anonymous store object
newStore = vm_store_create_anonymous_noswap(
(protection & B_STACK_AREA) != 0, 0, USER_STACK_GUARD_PAGES);
if (newStore == NULL) {
status = B_NO_MEMORY;
goto err1;
}
newCache = vm_cache_create(newStore);
if (newCache == NULL) {
status = B_NO_MEMORY;
newStore->ops->destroy(newStore);
goto err1;
}
mutex_lock(&newCache->lock);
newCache->type = CACHE_TYPE_RAM;
newCache->temporary = 1;
newCache->scan_skip = cache->scan_skip;
newCache->virtual_base = offset;
newCache->virtual_size = offset + size;
vm_cache_add_consumer_locked(cache, newCache);
cache = newCache;
store = newStore;
}
status = vm_cache_set_minimal_commitment_locked(cache, offset + size);
if (status != B_OK)
goto err2;
// check to see if this address space has entered DELETE state
if (addressSpace->state == VM_ASPACE_STATE_DELETION) {
// okay, someone is trying to delete this address space now, so we can't
// insert the area, so back out
status = B_BAD_TEAM_ID;
goto err2;
}
status = insert_area(addressSpace, _virtualAddress, addressSpec, size, area);
if (status < B_OK)
goto err2;
// attach the cache to the area
area->cache = cache;
area->cache_offset = offset;
// point the cache back to the area
vm_cache_insert_area_locked(cache, area);
if (mapping == REGION_PRIVATE_MAP)
mutex_unlock(&cache->lock);
// insert the area in the global area hash table
acquire_sem_etc(sAreaHashLock, WRITE_COUNT, 0 ,0);
hash_insert(sAreaHash, area);
release_sem_etc(sAreaHashLock, WRITE_COUNT, 0);
// grab a ref to the address space (the area holds this)
atomic_add(&addressSpace->ref_count, 1);
*_area = area;
return B_OK;
err2:
if (mapping == REGION_PRIVATE_MAP) {
// We created this cache, so we must delete it again. Note, that we
// need to temporarily unlock the source cache or we'll otherwise
// deadlock, since vm_cache_remove_consumer will try to lock it too.
mutex_unlock(&cache->lock);
mutex_unlock(&sourceCache->lock);
vm_cache_release_ref(cache);
mutex_lock(&sourceCache->lock);
}
err1:
free(area->name);
free(area);
return status;
}
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;
// check to see if this address space has entered DELETE state
if (locker.AddressSpace()->state == VM_ASPACE_STATE_DELETION) {
// okay, someone is trying to delete this address space now, so we can't
// insert the area, so back out
return B_BAD_TEAM_ID;
}
// search area list and remove any matching reserved ranges
vm_area* area = locker.AddressSpace()->areas;
vm_area* last = NULL;
while (area) {
// the area must be completely part of the reserved range
if (area->id == RESERVED_AREA_ID && area->base >= (addr_t)address
&& area->base + area->size <= (addr_t)address + size) {
// remove reserved range
vm_area *reserved = area;
if (last)
last->address_space_next = reserved->address_space_next;
else
locker.AddressSpace()->areas = reserved->address_space_next;
area = reserved->address_space_next;
vm_put_address_space(locker.AddressSpace());
free(reserved);
continue;
}
last = area;
area = area->address_space_next;
}
return B_OK;
}
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;
// check to see if this address space has entered DELETE state
if (locker.AddressSpace()->state == VM_ASPACE_STATE_DELETION) {
// okay, someone is trying to delete this address space now, so we
// can't insert the area, let's back out
return B_BAD_TEAM_ID;
}
vm_area *area = create_reserved_area_struct(locker.AddressSpace(), flags);
if (area == NULL)
return B_NO_MEMORY;
status_t status = insert_area(locker.AddressSpace(), _address, addressSpec,
size, area);
if (status < B_OK) {
free(area);
return status;
}
// the area is now reserved!
area->cache_offset = area->base;
// we cache the original base address here
atomic_add(&locker.AddressSpace()->ref_count, 1);
return B_OK;
}
area_id
vm_create_anonymous_area(team_id team, const char *name, void **address,
uint32 addressSpec, addr_t size, uint32 wiring, uint32 protection)
{
vm_area *area;
vm_cache *cache;
vm_store *store;
vm_page *page = NULL;
bool isStack = (protection & B_STACK_AREA) != 0;
bool canOvercommit = false;
TRACE(("create_anonymous_area %s: size 0x%lx\n", name, size));
if (size == 0)
return B_BAD_VALUE;
if (!arch_vm_supports_protection(protection))
return B_NOT_SUPPORTED;
if (isStack || (protection & B_OVERCOMMITTING_AREA) != 0)
canOvercommit = true;
#ifdef DEBUG_KERNEL_STACKS
if ((protection & B_KERNEL_STACK_AREA) != 0)
isStack = true;
#endif
/* check parameters */
switch (addressSpec) {
case B_ANY_ADDRESS:
case B_EXACT_ADDRESS:
case B_BASE_ADDRESS:
case B_ANY_KERNEL_ADDRESS:
break;
default:
return B_BAD_VALUE;
}
switch (wiring) {
case B_NO_LOCK:
case B_FULL_LOCK:
case B_LAZY_LOCK:
case B_CONTIGUOUS:
case B_ALREADY_WIRED:
break;
case B_LOMEM:
//case B_SLOWMEM:
dprintf("B_LOMEM/SLOWMEM is not yet supported!\n");
wiring = B_FULL_LOCK;
break;
default:
return B_BAD_VALUE;
}
AddressSpaceWriteLocker locker;
status_t status = locker.SetTo(team);
if (status != NULL)
return status;
vm_address_space *addressSpace = locker.AddressSpace();
size = PAGE_ALIGN(size);
if (wiring == B_CONTIGUOUS) {
// we try to allocate the page run here upfront as this may easily
// fail for obvious reasons
page = vm_page_allocate_page_run(PAGE_STATE_CLEAR, size / B_PAGE_SIZE);
if (page == NULL)
return B_NO_MEMORY;
}
// create an anonymous store object
// if it's a stack, make sure that two pages are available at least
store = vm_store_create_anonymous_noswap(canOvercommit, isStack ? 2 : 0,
isStack ? ((protection & B_USER_PROTECTION) != 0 ?
USER_STACK_GUARD_PAGES : KERNEL_STACK_GUARD_PAGES) : 0);
if (store == NULL) {
status = B_NO_MEMORY;
goto err1;
}
cache = vm_cache_create(store);
if (cache == NULL) {
status = B_NO_MEMORY;
goto err2;
}
cache->temporary = 1;
cache->type = CACHE_TYPE_RAM;
cache->virtual_size = size;
switch (wiring) {
case B_LAZY_LOCK:
case B_FULL_LOCK:
case B_CONTIGUOUS:
case B_ALREADY_WIRED:
cache->scan_skip = 1;
break;
case B_NO_LOCK:
cache->scan_skip = 0;
break;
}
mutex_lock(&cache->lock);
status = map_backing_store(addressSpace, cache, address, 0, size,
addressSpec, wiring, protection, REGION_NO_PRIVATE_MAP, &area, name);
mutex_unlock(&cache->lock);
if (status < B_OK) {
vm_cache_release_ref(cache);
goto err1;
}
locker.DegradeToReadLock();
switch (wiring) {
case B_NO_LOCK:
case B_LAZY_LOCK:
// do nothing - the pages are mapped in as needed
break;
case B_FULL_LOCK:
{
vm_translation_map *map = &addressSpace->translation_map;
size_t reservePages = map->ops->map_max_pages_need(map,
area->base, area->base + (area->size - 1));
vm_page_reserve_pages(reservePages);
// Allocate and map all pages for this area
mutex_lock(&cache->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(PAGE_STATE_CLEAR, false);
if (page == NULL) {
// this shouldn't really happen, as we reserve the memory upfront
panic("couldn't fulfill B_FULL lock!");
}
vm_cache_insert_page(cache, page, offset);
vm_map_page(area, page, address, protection);
}
mutex_unlock(&cache->lock);
vm_page_unreserve_pages(reservePages);
break;
}
case B_ALREADY_WIRED:
{
// the pages should already be mapped. This is only really useful during
// boot time. Find the appropriate vm_page objects and stick them in
// the cache object.
vm_translation_map *map = &addressSpace->translation_map;
off_t offset = 0;
if (!kernel_startup)
panic("ALREADY_WIRED flag used outside kernel startup\n");
mutex_lock(&cache->lock);
map->ops->lock(map);
for (addr_t virtualAddress = area->base; virtualAddress < area->base
+ (area->size - 1); virtualAddress += B_PAGE_SIZE,
offset += B_PAGE_SIZE) {
addr_t physicalAddress;
uint32 flags;
status = map->ops->query(map, 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 0x%lx\n",
physicalAddress);
}
page->wired_count++;
// TODO: needs to be atomic on all platforms!
vm_page_set_state(page, PAGE_STATE_WIRED);
vm_cache_insert_page(cache, page, offset);
}
map->ops->unlock(map);
mutex_unlock(&cache->lock);
break;
}
case B_CONTIGUOUS:
{
// We have already allocated our continuous pages run, so we can now just
// map them in the address space
vm_translation_map *map = &addressSpace->translation_map;
addr_t physicalAddress = page->physical_page_number * B_PAGE_SIZE;
addr_t virtualAddress = area->base;
size_t reservePages = map->ops->map_max_pages_need(map,
virtualAddress, virtualAddress + (area->size - 1));
off_t offset = 0;
vm_page_reserve_pages(reservePages);
mutex_lock(&cache->lock);
map->ops->lock(map);
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->ops->map(map, virtualAddress, physicalAddress,
protection);
if (status < B_OK)
panic("couldn't map physical page in page run\n");
page->wired_count++;
// TODO: needs to be atomic on all platforms!
vm_page_set_state(page, PAGE_STATE_WIRED);
vm_cache_insert_page(cache, page, offset);
}
map->ops->unlock(map);
mutex_unlock(&cache->lock);
vm_page_unreserve_pages(reservePages);
break;
}
default:
break;
}
TRACE(("vm_create_anonymous_area: done\n"));
area->cache_type = CACHE_TYPE_RAM;
return area->id;
err2:
store->ops->destroy(store);
err1:
if (wiring == B_CONTIGUOUS) {
// we had reserved the area space upfront...
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_set_state(page, PAGE_STATE_FREE);
}
}
return status;
}
area_id
vm_map_physical_memory(team_id team, const char *name, void **_address,
uint32 addressSpec, addr_t size, uint32 protection, addr_t physicalAddress)
{
vm_area *area;
vm_cache *cache;
vm_store *store;
addr_t mapOffset;
TRACE(("vm_map_physical_memory(aspace = %ld, \"%s\", virtual = %p, "
"spec = %ld, size = %lu, protection = %ld, phys = %#lx)\n", team,
name, _address, addressSpec, size, protection, physicalAddress));
if (!arch_vm_supports_protection(protection))
return B_NOT_SUPPORTED;
AddressSpaceWriteLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
// if the physical address is somewhat inside a page,
// move the actual area down to align on a page boundary
mapOffset = physicalAddress % B_PAGE_SIZE;
size += mapOffset;
physicalAddress -= mapOffset;
size = PAGE_ALIGN(size);
// create an device store object
store = vm_store_create_device(physicalAddress);
if (store == NULL)
return B_NO_MEMORY;
cache = vm_cache_create(store);
if (cache == NULL) {
store->ops->destroy(store);
return B_NO_MEMORY;
}
// tell the page scanner to skip over this area, it's pages are special
cache->scan_skip = 1;
cache->type = CACHE_TYPE_DEVICE;
cache->virtual_size = size;
mutex_lock(&cache->lock);
status_t status = map_backing_store(locker.AddressSpace(), cache, _address,
0, size, addressSpec & ~B_MTR_MASK, B_FULL_LOCK, protection,
REGION_NO_PRIVATE_MAP, &area, name);
mutex_unlock(&cache->lock);
if (status < B_OK)
vm_cache_release_ref(cache);
if (status >= B_OK && (addressSpec & B_MTR_MASK) != 0) {
// set requested memory type
status = arch_vm_set_memory_type(area, physicalAddress,
addressSpec & B_MTR_MASK);
if (status < B_OK)
delete_area(locker.AddressSpace(), area);
}
if (status >= B_OK) {
// make sure our area is mapped in completely
vm_translation_map *map = &locker.AddressSpace()->translation_map;
size_t reservePages = map->ops->map_max_pages_need(map, area->base,
area->base + (size - 1));
vm_page_reserve_pages(reservePages);
map->ops->lock(map);
for (addr_t offset = 0; offset < size; offset += B_PAGE_SIZE) {
map->ops->map(map, area->base + offset, physicalAddress + offset,
protection);
}
map->ops->unlock(map);
vm_page_unreserve_pages(reservePages);
}
if (status < B_OK)
return status;
// modify the pointer returned to be offset back into the new area
// the same way the physical address in was offset
*_address = (void *)((addr_t)*_address + mapOffset);
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)
{
vm_area *area;
vm_cache *cache;
vm_store *store;
status_t status;
AddressSpaceWriteLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
size = PAGE_ALIGN(size);
// create an null store object
store = vm_store_create_null();
if (store == NULL)
return B_NO_MEMORY;
cache = vm_cache_create(store);
if (cache == NULL) {
store->ops->destroy(store);
return B_NO_MEMORY;
}
// tell the page scanner to skip over this area, no pages will be mapped here
cache->scan_skip = 1;
cache->type = CACHE_TYPE_NULL;
cache->virtual_size = size;
mutex_lock(&cache->lock);
status = map_backing_store(locker.AddressSpace(), cache, address, 0, size,
addressSpec, 0, B_KERNEL_READ_AREA, REGION_NO_PRIVATE_MAP, &area, name);
mutex_unlock(&cache->lock);
if (status < B_OK) {
vm_cache_release_ref(cache);
return status;
}
area->cache_type = CACHE_TYPE_NULL;
return area->id;
}
/*! Creates the vnode cache for the specified \a vnode.
The vnode has to be marked busy when calling this function.
*/
status_t
vm_create_vnode_cache(struct vnode *vnode, struct vm_cache **_cache)
{
status_t status;
// create a vnode store object
vm_store *store = vm_create_vnode_store(vnode);
if (store == NULL)
return B_NO_MEMORY;
vm_cache *cache = vm_cache_create(store);
if (cache == NULL) {
status = B_NO_MEMORY;
goto err1;
}
cache->type = CACHE_TYPE_VNODE;
*_cache = cache;
return B_OK;
err1:
store->ops->destroy(store);
return status;
}
/*! Will map the file at the path specified by \a name to an area in memory.
The file will be mirrored beginning at the specified \a offset. The \a offset
and \a size arguments have to be page aligned.
*/
static area_id
_vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec,
size_t size, uint32 protection, uint32 mapping, const char *path,
off_t offset, bool kernel)
{
// ToDo: maybe attach to an FD, not a path (or both, like VFS calls)
// ToDo: check file access permissions (would be already done if the above were true)
// ToDo: for binary files, we want to make sure that they get the
// copy of a file at a given time, ie. later changes should not
// make it into the mapped copy -- this will need quite some changes
// to be done in a nice way
TRACE(("_vm_map_file(\"%s\", offset = %Ld, size = %lu, mapping %ld)\n",
path, offset, size, mapping));
offset = ROUNDOWN(offset, B_PAGE_SIZE);
size = PAGE_ALIGN(size);
// get the vnode for the object, this also grabs a ref to it
struct vnode *vnode;
status_t status = vfs_get_vnode_from_path(path, kernel, &vnode);
if (status < B_OK)
return status;
AddressSpaceWriteLocker locker(team);
if (!locker.IsLocked()) {
vfs_put_vnode(vnode);
return B_BAD_TEAM_ID;
}
// ToDo: this only works for file systems that use the file cache
vm_cache *cache;
status = vfs_get_vnode_cache(vnode, &cache, false);
if (status < B_OK) {
vfs_put_vnode(vnode);
return status;
}
mutex_lock(&cache->lock);
vm_area *area;
status = map_backing_store(locker.AddressSpace(), cache, _address,
offset, size, addressSpec, 0, protection, mapping, &area, name);
mutex_unlock(&cache->lock);
vfs_put_vnode(vnode);
// we don't need this vnode anymore - if the above call was
// successful, the store already has a ref to it
if (status < B_OK || mapping == REGION_PRIVATE_MAP) {
// map_backing_store() cannot know we no longer need the ref
vm_cache_release_ref(cache);
}
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, const char *path,
off_t offset)
{
if (!arch_vm_supports_protection(protection))
return B_NOT_SUPPORTED;
return _vm_map_file(aid, name, address, addressSpec, size, protection,
mapping, path, offset, true);
}
vm_cache *
vm_area_get_locked_cache(vm_area *area)
{
MutexLocker locker(sAreaCacheLock);
while (true) {
vm_cache* cache = area->cache;
vm_cache_acquire_ref(cache);
locker.Unlock();
mutex_lock(&cache->lock);
locker.Lock();
if (cache == area->cache)
return cache;
// the cache changed in the meantime
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
}
}
void
vm_area_put_locked_cache(vm_cache *cache)
{
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
}
area_id
vm_clone_area(team_id team, const char *name, void **address,
uint32 addressSpec, uint32 protection, uint32 mapping, area_id sourceID)
{
vm_area *newArea = NULL;
vm_area *sourceArea;
MultiAddressSpaceLocker locker;
vm_address_space *sourceAddressSpace;
status_t status = locker.AddArea(sourceID, false, &sourceAddressSpace);
if (status != B_OK)
return status;
vm_address_space *targetAddressSpace;
status = locker.AddTeam(team, true, &targetAddressSpace);
if (status != B_OK)
return status;
status = locker.Lock();
if (status != B_OK)
return status;
sourceArea = lookup_area(sourceAddressSpace, sourceID);
if (sourceArea == NULL)
return B_BAD_VALUE;
vm_cache *cache = vm_area_get_locked_cache(sourceArea);
// ToDo: for now, B_USER_CLONEABLE is disabled, until all drivers
// have been adapted. Maybe it should be part of the kernel settings,
// anyway (so that old drivers can always work).
#if 0
if (sourceArea->aspace == vm_kernel_address_space() && addressSpace != vm_kernel_address_space()
&& !(sourceArea->protection & B_USER_CLONEABLE_AREA)) {
// kernel areas must not be cloned in userland, unless explicitly
// declared user-cloneable upon construction
status = B_NOT_ALLOWED;
} else
#endif
if (sourceArea->cache_type == CACHE_TYPE_NULL)
status = B_NOT_ALLOWED;
else {
status = map_backing_store(targetAddressSpace, cache, address,
sourceArea->cache_offset, sourceArea->size, addressSpec,
sourceArea->wiring, protection, mapping, &newArea, name);
}
if (status == B_OK && mapping != REGION_PRIVATE_MAP) {
// If the mapping is REGION_PRIVATE_MAP, map_backing_store() needed
// to create a new ref, and has therefore already acquired a reference
// to the source cache - but otherwise it has no idea that we need
// one.
vm_cache_acquire_ref(cache);
}
if (status == B_OK && newArea->wiring == B_FULL_LOCK) {
// we need to map in everything at this point
if (sourceArea->cache_type == CACHE_TYPE_DEVICE) {
// we don't have actual pages to map but a physical area
vm_translation_map *map = &sourceArea->address_space->translation_map;
map->ops->lock(map);
addr_t physicalAddress;
uint32 oldProtection;
map->ops->query(map, sourceArea->base, &physicalAddress,
&oldProtection);
map->ops->unlock(map);
map = &targetAddressSpace->translation_map;
size_t reservePages = map->ops->map_max_pages_need(map,
newArea->base, newArea->base + (newArea->size - 1));
vm_page_reserve_pages(reservePages);
map->ops->lock(map);
for (addr_t offset = 0; offset < newArea->size;
offset += B_PAGE_SIZE) {
map->ops->map(map, newArea->base + offset,
physicalAddress + offset, protection);
}
map->ops->unlock(map);
vm_page_unreserve_pages(reservePages);
} else {
vm_translation_map *map = &targetAddressSpace->translation_map;
size_t reservePages = map->ops->map_max_pages_need(map,
newArea->base, newArea->base + (newArea->size - 1));
vm_page_reserve_pages(reservePages);
// map in all pages from source
for (vm_page *page = cache->page_list; page != NULL;
page = page->cache_next) {
vm_map_page(newArea, page, newArea->base
+ ((page->cache_offset << PAGE_SHIFT) - newArea->cache_offset),
protection);
}
vm_page_unreserve_pages(reservePages);
}
}
if (status == B_OK)
newArea->cache_type = sourceArea->cache_type;
vm_area_put_locked_cache(cache);
if (status < B_OK)
return status;
return newArea->id;
}
//! The address space must be write locked at this point
static void
remove_area_from_address_space(vm_address_space *addressSpace, vm_area *area)
{
vm_area *temp, *last = NULL;
temp = addressSpace->areas;
while (temp != NULL) {
if (area == temp) {
if (last != NULL) {
last->address_space_next = temp->address_space_next;
} else {
addressSpace->areas = temp->address_space_next;
}
addressSpace->change_count++;
break;
}
last = temp;
temp = temp->address_space_next;
}
if (area == addressSpace->area_hint)
addressSpace->area_hint = NULL;
if (temp == NULL)
panic("vm_area_release_ref: area not found in aspace's area list\n");
}
static void
delete_area(vm_address_space *addressSpace, vm_area *area)
{
acquire_sem_etc(sAreaHashLock, WRITE_COUNT, 0, 0);
hash_remove(sAreaHash, area);
release_sem_etc(sAreaHashLock, WRITE_COUNT, 0);
// At this point the area is removed from the global hash table, but
// still exists in the area list.
// Unmap the virtual address space the area occupied
vm_unmap_pages(area, area->base, area->size, !area->cache->temporary);
if (!area->cache->temporary)
vm_cache_write_modified(area->cache, false);
arch_vm_unset_memory_type(area);
remove_area_from_address_space(addressSpace, area);
vm_put_address_space(addressSpace);
vm_cache_remove_area(area->cache, area);
vm_cache_release_ref(area->cache);
free(area->name);
free(area);
}
status_t
vm_delete_area(team_id team, area_id id)
{
TRACE(("vm_delete_area(team = 0x%lx, area = 0x%lx)\n", team, id));
AddressSpaceWriteLocker locker;
vm_area *area;
status_t status = locker.SetFromArea(team, id, area);
if (status < B_OK)
return status;
delete_area(locker.AddressSpace(), area);
return B_OK;
}
/*! Creates a new cache on top of given cache, moves all areas from
the old cache to the new one, and changes the protection of all affected
areas' pages to read-only.
Preconditions:
- The given cache must be locked.
- All of the cache's areas' address spaces must be read locked.
- All of the cache's areas must have a clear \c no_cache_change flags.
*/
static status_t
vm_copy_on_write_area(vm_cache* lowerCache)
{
vm_store *store;
vm_cache *upperCache;
vm_page *page;
status_t status;
TRACE(("vm_copy_on_write_area(area = %p)\n", area));
// We need to separate the cache from its areas. The cache goes one level
// deeper and we create a new cache inbetween.
// create an anonymous store object
store = vm_store_create_anonymous_noswap(false, 0, 0);
if (store == NULL)
return B_NO_MEMORY;
upperCache = vm_cache_create(store);
if (upperCache == NULL) {
store->ops->destroy(store);
return B_NO_MEMORY;
}
mutex_lock(&upperCache->lock);
upperCache->type = CACHE_TYPE_RAM;
upperCache->temporary = 1;
upperCache->scan_skip = lowerCache->scan_skip;
upperCache->virtual_base = lowerCache->virtual_base;
upperCache->virtual_size = lowerCache->virtual_size;
// transfer the lower cache areas to the upper cache
mutex_lock(&sAreaCacheLock);
upperCache->areas = lowerCache->areas;
lowerCache->areas = NULL;
for (vm_area *tempArea = upperCache->areas; tempArea != NULL;
tempArea = tempArea->cache_next) {
ASSERT(!tempArea->no_cache_change);
tempArea->cache = upperCache;
atomic_add(&upperCache->ref_count, 1);
atomic_add(&lowerCache->ref_count, -1);
}
mutex_unlock(&sAreaCacheLock);
vm_cache_add_consumer_locked(lowerCache, upperCache);
// We now need to remap all pages from all of the cache's areas read-only, so that
// a copy will be created on next write access
for (vm_area *tempArea = upperCache->areas; tempArea != NULL;
tempArea = tempArea->cache_next) {
// The area must be readable in the same way it was previously writable
uint32 protection = B_KERNEL_READ_AREA;
if (tempArea->protection & B_READ_AREA)
protection |= B_READ_AREA;
vm_translation_map *map = &tempArea->address_space->translation_map;
map->ops->lock(map);
map->ops->protect(map, tempArea->base, tempArea->base - 1 + tempArea->size, protection);
map->ops->unlock(map);
}
vm_area_put_locked_cache(upperCache);
return B_OK;
}
area_id
vm_copy_area(team_id team, const char *name, void **_address,
uint32 addressSpec, uint32 protection, area_id sourceID)
{
bool writableCopy = (protection & (B_KERNEL_WRITE_AREA | B_WRITE_AREA)) != 0;
if ((protection & B_KERNEL_PROTECTION) == 0) {
// set the same protection for the kernel as for userland
protection |= B_KERNEL_READ_AREA;
if (writableCopy)
protection |= B_KERNEL_WRITE_AREA;
}
// Do the locking: target address space, all address spaces associated with
// the source cache, and the cache itself.
MultiAddressSpaceLocker locker;
vm_address_space *targetAddressSpace;
vm_cache *cache;
vm_area* source;
status_t status = locker.AddTeam(team, true, &targetAddressSpace);
if (status == B_OK) {
status = locker.AddAreaCacheAndLock(sourceID, false, false, source,
&cache, true);
}
if (status != B_OK)
return status;
AreaCacheLocker cacheLocker(cache); // already locked
if (addressSpec == B_CLONE_ADDRESS) {
addressSpec = B_EXACT_ADDRESS;
*_address = (void *)source->base;
}
// First, create a cache on top of the source area
vm_area *target;
status = map_backing_store(targetAddressSpace, cache, _address,
source->cache_offset, source->size, addressSpec, source->wiring,
protection, REGION_PRIVATE_MAP, &target, name);
if (status < B_OK)
return status;
// If the source area is writable, we need to move it one layer up as well
if ((source->protection & (B_KERNEL_WRITE_AREA | B_WRITE_AREA)) != 0) {
// ToDo: do something more useful if this fails!
if (vm_copy_on_write_area(cache) < B_OK)
panic("vm_copy_on_write_area() failed!\n");
}
// we return the ID of the newly created area
return target->id;
}
//! You need to hold the cache lock when calling this function
static int32
count_writable_areas(vm_cache *cache, vm_area *ignoreArea)
{
struct vm_area *area = cache->areas;
uint32 count = 0;
for (; area != NULL; area = area->cache_next) {
if (area != ignoreArea
&& (area->protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0)
count++;
}
return count;
}
static status_t
vm_set_area_protection(team_id team, area_id areaID, uint32 newProtection)
{
TRACE(("vm_set_area_protection(team = %#lx, area = %#lx, protection = %#lx)\n",
team, areaID, newProtection));
if (!arch_vm_supports_protection(newProtection))
return B_NOT_SUPPORTED;
// lock address spaces and cache
MultiAddressSpaceLocker locker;
vm_cache *cache;
vm_area* area;
status_t status = locker.AddAreaCacheAndLock(areaID, true, false, area,
&cache, true);
AreaCacheLocker cacheLocker(cache); // already locked
if (area->protection == newProtection)
return B_OK;
if (team != vm_kernel_address_space_id()
&& area->address_space->id != team) {
// unless you're the kernel, you are only allowed to set
// the protection of your own areas
return B_NOT_ALLOWED;
}
bool changePageProtection = true;
if ((area->protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0
&& (newProtection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) == 0) {
// writable -> !writable
if (cache->source != NULL && cache->temporary) {
if (count_writable_areas(cache, area) == 0) {
// Since this cache now lives from the pages in its source cache,
// we can change the cache's commitment to take only those pages
// into account that really are in this cache.
// count existing pages in this cache
struct vm_page *page = cache->page_list;
uint32 count = 0;
for (; page != NULL; page = page->cache_next) {
count++;
}
status = cache->store->ops->commit(cache->store,
cache->virtual_base + count * B_PAGE_SIZE);
// ToDo: we may be able to join with our source cache, if count == 0
}
}
} else if ((area->protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) == 0
&& (newProtection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0) {
// !writable -> writable
if (!list_is_empty(&cache->consumers)) {
// There are consumers -- we have to insert a new cache. Fortunately
// vm_copy_on_write_area() does everything that's needed.
changePageProtection = false;
status = vm_copy_on_write_area(cache);
} else {
// No consumers, so we don't need to insert a new one.
if (cache->source != NULL && cache->temporary) {
// the cache's commitment must contain all possible pages
status = cache->store->ops->commit(cache->store,
cache->virtual_size);
}
if (status == B_OK && cache->source != NULL) {
// There's a source cache, hence we can't just change all pages'
// protection or we might allow writing into pages belonging to
// a lower cache.
changePageProtection = false;
struct vm_translation_map *map
= &area->address_space->translation_map;
map->ops->lock(map);
vm_page* page = cache->page_list;
while (page) {
addr_t address = area->base
+ (page->cache_offset << PAGE_SHIFT);
map->ops->protect(map, address, address - 1 + B_PAGE_SIZE,
newProtection);
page = page->cache_next;
}
map->ops->unlock(map);
}
}
} else {
// we don't have anything special to do in all other cases
}
if (status == B_OK) {
// remap existing pages in this cache
struct vm_translation_map *map = &area->address_space->translation_map;
if (changePageProtection) {
map->ops->lock(map);
map->ops->protect(map, area->base, area->base + area->size,
newProtection);
map->ops->unlock(map);
}
area->protection = newProtection;
}
return status;
}
status_t
vm_get_page_mapping(team_id team, addr_t vaddr, addr_t *paddr)
{
vm_address_space *addressSpace = vm_get_address_space_by_id(team);
if (addressSpace == NULL)
return B_BAD_TEAM_ID;
uint32 dummyFlags;
status_t status = addressSpace->translation_map.ops->query(
&addressSpace->translation_map, vaddr, paddr, &dummyFlags);
vm_put_address_space(addressSpace);
return status;
}
static inline addr_t
virtual_page_address(vm_area *area, vm_page *page)
{
return area->base
+ ((page->cache_offset << PAGE_SHIFT) - area->cache_offset);
}
bool
vm_test_map_modification(vm_page *page)
{
MutexLocker locker(sMappingLock);
vm_page_mappings::Iterator iterator = page->mappings.GetIterator();
vm_page_mapping *mapping;
while ((mapping = iterator.Next()) != NULL) {
vm_area *area = mapping->area;
vm_translation_map *map = &area->address_space->translation_map;
addr_t physicalAddress;
uint32 flags;
map->ops->lock(map);
map->ops->query(map, virtual_page_address(area, page),
&physicalAddress, &flags);
map->ops->unlock(map);
if (flags & PAGE_MODIFIED)
return true;
}
return false;
}
int32
vm_test_map_activation(vm_page *page, bool *_modified)
{
int32 activation = 0;
bool modified = false;
MutexLocker locker(sMappingLock);
vm_page_mappings::Iterator iterator = page->mappings.GetIterator();
vm_page_mapping *mapping;
while ((mapping = iterator.Next()) != NULL) {
vm_area *area = mapping->area;
vm_translation_map *map = &area->address_space->translation_map;
addr_t physicalAddress;
uint32 flags;
map->ops->lock(map);
map->ops->query(map, virtual_page_address(area, page),
&physicalAddress, &flags);
map->ops->unlock(map);
if (flags & PAGE_ACCESSED)
activation++;
if (flags & PAGE_MODIFIED)
modified = true;
}
if (_modified != NULL)
*_modified = modified;
return activation;
}
void
vm_clear_map_flags(vm_page *page, uint32 flags)
{
MutexLocker locker(sMappingLock);
vm_page_mappings::Iterator iterator = page->mappings.GetIterator();
vm_page_mapping *mapping;
while ((mapping = iterator.Next()) != NULL) {
vm_area *area = mapping->area;
vm_translation_map *map = &area->address_space->translation_map;
map->ops->lock(map);
map->ops->clear_flags(map, virtual_page_address(area, page), flags);
map->ops->unlock(map);
}
}
/*! Removes all mappings from a page.
After you've called this function, the page is unmapped from memory.
The accumulated page flags of all mappings can be found in \a _flags.
*/
void
vm_remove_all_page_mappings(vm_page *page, uint32 *_flags)
{
uint32 accumulatedFlags = 0;
MutexLocker locker(sMappingLock);
vm_page_mappings queue;
queue.MoveFrom(&page->mappings);
vm_page_mappings::Iterator iterator = queue.GetIterator();
vm_page_mapping *mapping;
while ((mapping = iterator.Next()) != NULL) {
vm_area *area = mapping->area;
vm_translation_map *map = &area->address_space->translation_map;
addr_t physicalAddress;
uint32 flags;
map->ops->lock(map);
addr_t address = virtual_page_address(area, page);
map->ops->unmap(map, address, address + (B_PAGE_SIZE - 1));
map->ops->flush(map);
map->ops->query(map, address, &physicalAddress, &flags);
map->ops->unlock(map);
area->mappings.Remove(mapping);
accumulatedFlags |= flags;
}
locker.Unlock();
// free now unused mappings
while ((mapping = queue.RemoveHead()) != NULL) {
free(mapping);
}
if (_flags != NULL)
*_flags = accumulatedFlags;
}
status_t
vm_unmap_pages(vm_area *area, addr_t base, size_t size, bool preserveModified)
{
vm_translation_map *map = &area->address_space->translation_map;
addr_t end = base + (size - 1);
map->ops->lock(map);
if (area->wiring != B_NO_LOCK && area->cache_type != CACHE_TYPE_DEVICE) {
// iterate through all pages and decrease their wired count
for (addr_t virtualAddress = base; virtualAddress < end;
virtualAddress += B_PAGE_SIZE) {
addr_t physicalAddress;
uint32 flags;
status_t status = map->ops->query(map, virtualAddress,
&physicalAddress, &flags);
if (status < B_OK || (flags & PAGE_PRESENT) == 0)
continue;
vm_page *page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL) {
panic("area %p looking up page failed for pa 0x%lx\n", area,
physicalAddress);
}
page->wired_count--;
// TODO: needs to be atomic on all platforms!
}
}
map->ops->unmap(map, base, end);
if (preserveModified) {
map->ops->flush(map);
for (addr_t virtualAddress = base; virtualAddress < end;
virtualAddress += B_PAGE_SIZE) {
addr_t physicalAddress;
uint32 flags;
status_t status = map->ops->query(map, virtualAddress,
&physicalAddress, &flags);
if (status < B_OK || (flags & PAGE_PRESENT) == 0)
continue;
vm_page *page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL) {
panic("area %p looking up page failed for pa 0x%lx\n", area,
physicalAddress);
}
if ((flags & PAGE_MODIFIED) != 0
&& page->state != PAGE_STATE_MODIFIED)
vm_page_set_state(page, PAGE_STATE_MODIFIED);
}
}
map->ops->unlock(map);
if (area->wiring == B_NO_LOCK) {
uint32 startOffset = (area->cache_offset + base - area->base)
>> PAGE_SHIFT;
uint32 endOffset = startOffset + (size >> PAGE_SHIFT);
vm_page_mapping *mapping;
vm_area_mappings queue;
mutex_lock(&sMappingLock);
map->ops->lock(map);
vm_area_mappings::Iterator iterator = area->mappings.GetIterator();
while (iterator.HasNext()) {
mapping = iterator.Next();
vm_page *page = mapping->page;
if (page->cache_offset < startOffset
|| page->cache_offset >= endOffset)
continue;
mapping->page->mappings.Remove(mapping);
iterator.Remove();
queue.Add(mapping);
}
map->ops->unlock(map);
mutex_unlock(&sMappingLock);
while ((mapping = queue.RemoveHead()) != NULL) {
free(mapping);
}
}
return B_OK;
}
/*! When calling this function, you need to have pages reserved! */
status_t
vm_map_page(vm_area *area, vm_page *page, addr_t address, uint32 protection)
{
vm_translation_map *map = &area->address_space->translation_map;
vm_page_mapping *mapping = NULL;
if (area->wiring == B_NO_LOCK) {
mapping = (vm_page_mapping *)malloc(sizeof(vm_page_mapping));
if (mapping == NULL)
return B_NO_MEMORY;
mapping->page = page;
mapping->area = area;
}
map->ops->lock(map);
map->ops->map(map, address, page->physical_page_number * B_PAGE_SIZE,
protection);
map->ops->unlock(map);
if (area->wiring != B_NO_LOCK) {
page->wired_count++;
// TODO: needs to be atomic on all platforms!
} else {
// insert mapping into lists
MutexLocker locker(sMappingLock);
page->mappings.Add(mapping);
area->mappings.Add(mapping);
}
if (page->usage_count < 0)
page->usage_count = 1;
if (page->state != PAGE_STATE_MODIFIED)
vm_page_set_state(page, PAGE_STATE_ACTIVE);
return B_OK;
}
static int
display_mem(int argc, char **argv)
{
bool physical = false;
addr_t copyAddress;
int32 displayWidth;
int32 itemSize;
int32 num = -1;
addr_t address;
int i = 1, j;
if (argc > 1 && argv[1][0] == '-') {
if (!strcmp(argv[1], "-p") || !strcmp(argv[1], "--physical")) {
physical = true;
i++;
} else
i = 99;
}
if (argc < i + 1 || argc > i + 2) {
kprintf("usage: dl/dw/ds/db [-p|--physical] <address> [num]\n"
"\tdl - 8 bytes\n"
"\tdw - 4 bytes\n"
"\tds - 2 bytes\n"
"\tdb - 1 byte\n"
" -p or --physical only allows memory from a single page to be displayed.\n");
return 0;
}
address = strtoul(argv[i], NULL, 0);
if (argc > i + 1)
num = atoi(argv[i + 1]);
// build the format string
if (strcmp(argv[0], "db") == 0) {
itemSize = 1;
displayWidth = 16;
} else if (strcmp(argv[0], "ds") == 0) {
itemSize = 2;
displayWidth = 8;
} else if (strcmp(argv[0], "dw") == 0) {
itemSize = 4;
displayWidth = 4;
} else if (strcmp(argv[0], "dl") == 0) {
itemSize = 8;
displayWidth = 2;
} else {
kprintf("display_mem called in an invalid way!\n");
return 0;
}
if (num <= 0)
num = displayWidth;
if (physical) {
int32 offset = address & (B_PAGE_SIZE - 1);
if (num * itemSize + offset > B_PAGE_SIZE) {
num = (B_PAGE_SIZE - offset) / itemSize;
kprintf("NOTE: number of bytes has been cut to page size\n");
}
address = ROUNDOWN(address, B_PAGE_SIZE);
kernel_startup = true;
// vm_get_physical_page() needs to lock...
if (vm_get_physical_page(address, &copyAddress, PHYSICAL_PAGE_NO_WAIT) != B_OK) {
kprintf("getting the hardware page failed.");
kernel_startup = false;
return 0;
}
kernel_startup = false;
address += offset;
copyAddress += offset;
} else
copyAddress = address;
for (i = 0; i < num; i++) {
uint32 value;
if ((i % displayWidth) == 0) {
int32 displayed = min_c(displayWidth, (num-i)) * itemSize;
if (i != 0)
kprintf("\n");
kprintf("[0x%lx] ", address + i * itemSize);
for (j = 0; j < displayed; j++) {
char c;
if (user_memcpy(&c, (char *)copyAddress + i * itemSize + j, 1) != B_OK) {
displayed = j;
break;
}
if (!isprint(c))
c = '.';
kprintf("%c", c);
}
if (num > displayWidth) {
// make sure the spacing in the last line is correct
for (j = displayed; j < displayWidth * itemSize; j++)
kprintf(" ");
}
kprintf(" ");
}
if (user_memcpy(&value, (uint8 *)copyAddress + i * itemSize, itemSize) != B_OK) {
kprintf("read fault");
break;
}
switch (itemSize) {
case 1:
kprintf(" %02x", *(uint8 *)&value);
break;
case 2:
kprintf(" %04x", *(uint16 *)&value);
break;
case 4:
kprintf(" %08lx", *(uint32 *)&value);
break;
case 8:
kprintf(" %016Lx", *(uint64 *)&value);
break;
}
}
kprintf("\n");
if (physical) {
copyAddress = ROUNDOWN(copyAddress, B_PAGE_SIZE);
kernel_startup = true;
vm_put_physical_page(copyAddress);
kernel_startup = false;
}
return 0;
}
static void
dump_cache_tree_recursively(vm_cache* cache, int level,
vm_cache* highlightCache)
{
// print this cache
for (int i = 0; i < level; i++)
kprintf(" ");
if (cache == highlightCache)
kprintf("%p <--\n", cache);
else
kprintf("%p\n", cache);
// recursively print its consumers
vm_cache* consumer = NULL;
while ((consumer = (vm_cache *)list_get_next_item(&cache->consumers,
consumer)) != NULL) {
dump_cache_tree_recursively(consumer, level + 1, highlightCache);
}
}
static int
dump_cache_tree(int argc, char **argv)
{
if (argc < 2 || strlen(argv[1]) < 2
|| argv[1][0] != '0'
|| argv[1][1] != 'x') {
kprintf("%s: invalid argument, pass address\n", argv[0]);
return 0;
}
addr_t address = strtoul(argv[1], NULL, 0);
if (address == NULL)
return 0;
vm_cache *cache = (vm_cache *)address;
vm_cache *root = cache;
// find the root cache (the transitive source)
while (root->source != NULL)
root = root->source;
dump_cache_tree_recursively(root, 0, cache);
return 0;
}
#if DEBUG_CACHE_LIST
static int
dump_caches(int argc, char **argv)
{
kprintf("caches:");
vm_cache* cache = gDebugCacheList;
while (cache) {
kprintf(" %p", cache);
cache = cache->debug_next;
}
kprintf("\n");
return 0;
}
#endif // DEBUG_CACHE_LIST
static const char *
cache_type_to_string(int32 type)
{
switch (type) {
case CACHE_TYPE_RAM:
return "RAM";
case CACHE_TYPE_DEVICE:
return "device";
case CACHE_TYPE_VNODE:
return "vnode";
case CACHE_TYPE_NULL:
return "null";
default:
return "unknown";
}
}
static int
dump_cache(int argc, char **argv)
{
vm_cache *cache;
bool showPages = false;
int i = 1;
if (argc < 2) {
kprintf("usage: %s [-ps] <address>\n"
" if -p is specified, all pages are shown, if -s is used\n"
" only the cache info is shown respectively.\n", argv[0]);
return 0;
}
while (argv[i][0] == '-') {
char *arg = argv[i] + 1;
while (arg[0]) {
if (arg[0] == 'p')
showPages = true;
arg++;
}
i++;
}
if (argv[i] == NULL || strlen(argv[i]) < 2
|| argv[i][0] != '0'
|| argv[i][1] != 'x') {
kprintf("%s: invalid argument, pass address\n", argv[0]);
return 0;
}
addr_t address = strtoul(argv[i], NULL, 0);
if (address == NULL)
return 0;
cache = (vm_cache *)address;
kprintf("CACHE %p:\n", cache);
kprintf(" ref_count: %ld\n", cache->ref_count);
kprintf(" source: %p\n", cache->source);
kprintf(" store: %p\n", cache->store);
kprintf(" type: %s\n", cache_type_to_string(cache->type));
kprintf(" virtual_base: 0x%Lx\n", cache->virtual_base);
kprintf(" virtual_size: 0x%Lx\n", cache->virtual_size);
kprintf(" temporary: %ld\n", cache->temporary);
kprintf(" scan_skip: %ld\n", cache->scan_skip);
kprintf(" lock.holder: %ld\n", cache->lock.holder);
kprintf(" lock.sem: 0x%lx\n", cache->lock.sem);
kprintf(" areas:\n");
for (vm_area *area = cache->areas; area != NULL; area = area->cache_next) {
kprintf(" area 0x%lx, %s\n", area->id, area->name);
kprintf("\tbase_addr: 0x%lx, size: 0x%lx\n", area->base, area->size);
kprintf("\tprotection: 0x%lx\n", area->protection);
kprintf("\towner: 0x%lx\n", area->address_space->id);
}
kprintf(" consumers:\n");
vm_cache *consumer = NULL;
while ((consumer = (vm_cache *)list_get_next_item(&cache->consumers, consumer)) != NULL) {
kprintf("\t%p\n", consumer);
}
kprintf(" pages:\n");
int32 count = 0;
for (vm_page *page = cache->page_list; page != NULL; page = page->cache_next) {
count++;
if (!showPages)
continue;
if (page->type == PAGE_TYPE_PHYSICAL) {
kprintf("\t%p ppn 0x%lx offset 0x%lx type %u state %u (%s) wired_count %u\n",
page, page->physical_page_number, page->cache_offset, page->type, page->state,
page_state_to_string(page->state), page->wired_count);
} else if(page->type == PAGE_TYPE_DUMMY) {
kprintf("\t%p DUMMY PAGE state %u (%s)\n",
page, page->state, page_state_to_string(page->state));
} else
kprintf("\t%p UNKNOWN PAGE type %u\n", page, page->type);
}
if (!showPages)
kprintf("\t%ld in cache\n", count);
return 0;
}
static void
dump_area_struct(vm_area *area, bool mappings)
{
kprintf("AREA: %p\n", area);
kprintf("name:\t\t'%s'\n", area->name);
kprintf("owner:\t\t0x%lx\n", area->address_space->id);
kprintf("id:\t\t0x%lx\n", area->id);
kprintf("base:\t\t0x%lx\n", area->base);
kprintf("size:\t\t0x%lx\n", area->size);
kprintf("protection:\t0x%lx\n", area->protection);
kprintf("wiring:\t\t0x%x\n", area->wiring);
kprintf("memory_type:\t0x%x\n", area->memory_type);
kprintf("cache:\t\t%p\n", area->cache);
kprintf("cache_type:\t%s\n", cache_type_to_string(area->cache_type));
kprintf("cache_offset:\t0x%Lx\n", area->cache_offset);
kprintf("cache_next:\t%p\n", area->cache_next);
kprintf("cache_prev:\t%p\n", area->cache_prev);
vm_area_mappings::Iterator iterator = area->mappings.GetIterator();
if (mappings) {
kprintf("page mappings:\n");
while (iterator.HasNext()) {
vm_page_mapping *mapping = iterator.Next();
kprintf(" %p", mapping->page);
}
kprintf("\n");
} else {
uint32 count = 0;
while (iterator.Next() != NULL) {
count++;
}
kprintf("page mappings:\t%lu\n", count);
}
}
static int
dump_area(int argc, char **argv)
{
bool mappings = false;
bool found = false;
int32 index = 1;
vm_area *area;
addr_t num;
if (argc < 2) {
kprintf("usage: area [-m] <id|address|name>\n");
return 0;
}
if (!strcmp(argv[1], "-m")) {
mappings = true;
index++;
}
num = strtoul(argv[index], NULL, 0);
// walk through the area list, looking for the arguments as a name
struct hash_iterator iter;
hash_open(sAreaHash, &iter);
while ((area = (vm_area *)hash_next(sAreaHash, &iter)) != NULL) {
if ((area->name != NULL && !strcmp(argv[index], area->name))
|| num != 0
&& ((addr_t)area->id == num
|| area->base <= num && area->base + area->size > num)) {
dump_area_struct(area, mappings);
found = true;
}
}
if (!found)
kprintf("could not find area %s (%ld)\n", argv[index], num);
return 0;
}
static int
dump_area_list(int argc, char **argv)
{
vm_area *area;
struct hash_iterator iter;
const char *name = NULL;
int32 id = 0;
if (argc > 1) {
id = strtoul(argv[1], NULL, 0);
if (id == 0)
name = argv[1];
}
kprintf("addr id base\t\tsize protect lock name\n");
hash_open(sAreaHash, &iter);
while ((area = (vm_area *)hash_next(sAreaHash, &iter)) != NULL) {
if (id != 0 && area->address_space->id != id
|| name != NULL && strstr(area->name, name) == NULL)
continue;
kprintf("%p %5lx %p\t%p %4lx\t%4d %s\n", area, area->id, (void *)area->base,
(void *)area->size, area->protection, area->wiring, area->name);
}
hash_close(sAreaHash, &iter, false);
return 0;
}
static int
dump_available_memory(int argc, char **argv)
{
kprintf("Available memory: %Ld/%lu bytes\n",
sAvailableMemory, vm_page_num_pages() * B_PAGE_SIZE);
return 0;
}
status_t
vm_delete_areas(struct vm_address_space *addressSpace)
{
vm_area *area;
vm_area *next, *last = NULL;
TRACE(("vm_delete_areas: called on address space 0x%lx\n",
addressSpace->id));
acquire_sem_etc(addressSpace->sem, WRITE_COUNT, 0, 0);
// remove all reserved areas in this address space
for (area = addressSpace->areas; area; area = next) {
next = area->address_space_next;
if (area->id == RESERVED_AREA_ID) {
// just remove it
if (last)
last->address_space_next = area->address_space_next;
else
addressSpace->areas = area->address_space_next;
vm_put_address_space(addressSpace);
free(area);
continue;
}
last = area;
}
// delete all the areas in this address space
for (area = addressSpace->areas; area; area = next) {
next = area->address_space_next;
delete_area(addressSpace, area);
}
release_sem_etc(addressSpace->sem, WRITE_COUNT, 0);
return B_OK;
}
static area_id
vm_area_for(team_id team, addr_t address)
{
AddressSpaceReadLocker locker(team);
if (!locker.IsLocked())
return B_BAD_TEAM_ID;
vm_area *area = vm_area_lookup(locker.AddressSpace(), address);
if (area != NULL)
return area->id;
return B_ERROR;
}
/*!
Frees physical pages that were used during the boot process.
*/
static void
unmap_and_free_physical_pages(vm_translation_map *map, addr_t start, addr_t end)
{
// free all physical pages in the specified range
for (addr_t current = start; current < end; current += B_PAGE_SIZE) {
addr_t physicalAddress;
uint32 flags;
if (map->ops->query(map, current, &physicalAddress, &flags) == B_OK) {
vm_page *page = vm_lookup_page(current / B_PAGE_SIZE);
if (page != NULL)
vm_page_set_state(page, PAGE_STATE_FREE);
}
}
// unmap the memory
map->ops->unmap(map, start, end - 1);
}
void
vm_free_unused_boot_loader_range(addr_t start, addr_t size)
{
vm_translation_map *map = &vm_kernel_address_space()->translation_map;
addr_t end = start + size;
addr_t lastEnd = start;
vm_area *area;
TRACE(("vm_free_unused_boot_loader_range(): asked to free %p - %p\n", (void *)start, (void *)end));
// The areas are sorted in virtual address space order, so
// we just have to find the holes between them that fall
// into the area we should dispose
map->ops->lock(map);
for (area = vm_kernel_address_space()->areas; area; area = area->address_space_next) {
addr_t areaStart = area->base;
addr_t areaEnd = areaStart + area->size;
if (area->id == RESERVED_AREA_ID)
continue;
if (areaEnd >= end) {
// we are done, the areas are already beyond of what we have to free
lastEnd = end;
break;
}
if (areaStart > lastEnd) {
// this is something we can free
TRACE(("free boot range: get rid of %p - %p\n", (void *)lastEnd, (void *)areaStart));
unmap_and_free_physical_pages(map, lastEnd, areaStart);
}
lastEnd = areaEnd;
}
if (lastEnd < end) {
// we can also get rid of some space at the end of the area
TRACE(("free boot range: also remove %p - %p\n", (void *)lastEnd, (void *)end));
unmap_and_free_physical_pages(map, lastEnd, end);
}
map->ops->unlock(map);
}
static void
create_preloaded_image_areas(struct preloaded_image *image)
{
char name[B_OS_NAME_LENGTH];
void *address;
int32 length;
// use file name to create a good area name
char *fileName = strrchr(image->name, '/');
if (fileName == NULL)
fileName = image->name;
else
fileName++;
length = strlen(fileName);
// make sure there is enough space for the suffix
if (length > 25)
length = 25;
memcpy(name, fileName, length);
strcpy(name + length, "_text");
address = (void *)ROUNDOWN(image->text_region.start, B_PAGE_SIZE);
image->text_region.id = create_area(name, &address, B_EXACT_ADDRESS,
PAGE_ALIGN(image->text_region.size), B_ALREADY_WIRED,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
// this will later be remapped read-only/executable by the
// ELF initialization code
strcpy(name + length, "_data");
address = (void *)ROUNDOWN(image->data_region.start, B_PAGE_SIZE);
image->data_region.id = create_area(name, &address, B_EXACT_ADDRESS,
PAGE_ALIGN(image->data_region.size), B_ALREADY_WIRED,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
}
/** Frees all previously kernel arguments areas from the kernel_args structure.
* Any boot loader resources contained in that arguments must not be accessed
* anymore past this point.
*/
void
vm_free_kernel_args(kernel_args *args)
{
uint32 i;
TRACE(("vm_free_kernel_args()\n"));
for (i = 0; i < args->num_kernel_args_ranges; i++) {
area_id area = area_for((void *)args->kernel_args_range[i].start);
if (area >= B_OK)
delete_area(area);
}
}
static void
allocate_kernel_args(kernel_args *args)
{
uint32 i;
TRACE(("allocate_kernel_args()\n"));
for (i = 0; i < args->num_kernel_args_ranges; i++) {
void *address = (void *)args->kernel_args_range[i].start;
create_area("_kernel args_", &address, B_EXACT_ADDRESS, args->kernel_args_range[i].size,
B_ALREADY_WIRED, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
}
}
static void
unreserve_boot_loader_ranges(kernel_args *args)
{
uint32 i;
TRACE(("unreserve_boot_loader_ranges()\n"));
for (i = 0; i < args->num_virtual_allocated_ranges; i++) {
vm_unreserve_address_range(vm_kernel_address_space_id(),
(void *)args->virtual_allocated_range[i].start,
args->virtual_allocated_range[i].size);
}
}
static void
reserve_boot_loader_ranges(kernel_args *args)
{
uint32 i;
TRACE(("reserve_boot_loader_ranges()\n"));
for (i = 0; i < args->num_virtual_allocated_ranges; i++) {
void *address = (void *)args->virtual_allocated_range[i].start;
// If the address is no kernel address, we just skip it. The
// architecture specific code has to deal with it.
if (!IS_KERNEL_ADDRESS(address)) {
dprintf("reserve_boot_loader_ranges(): Skipping range: %p, %lu\n",
address, args->virtual_allocated_range[i].size);
continue;
}
status_t status = vm_reserve_address_range(vm_kernel_address_space_id(), &address,
B_EXACT_ADDRESS, args->virtual_allocated_range[i].size, 0);
if (status < B_OK)
panic("could not reserve boot loader ranges\n");
}
}
static addr_t
allocate_early_virtual(kernel_args *args, size_t size)
{
addr_t spot = 0;
uint32 i;
int last_valloc_entry = 0;
size = PAGE_ALIGN(size);
// find a slot in the virtual allocation addr range
for (i = 1; i < args->num_virtual_allocated_ranges; i++) {
addr_t previousRangeEnd = args->virtual_allocated_range[i - 1].start
+ args->virtual_allocated_range[i - 1].size;
last_valloc_entry = i;
// check to see if the space between this one and the last is big enough
if (previousRangeEnd >= KERNEL_BASE
&& args->virtual_allocated_range[i].start
- previousRangeEnd >= size) {
spot = previousRangeEnd;
args->virtual_allocated_range[i - 1].size += size;
goto out;
}
}
if (spot == 0) {
// we hadn't found one between allocation ranges. this is ok.
// see if there's a gap after the last one
addr_t lastRangeEnd
= args->virtual_allocated_range[last_valloc_entry].start
+ args->virtual_allocated_range[last_valloc_entry].size;
if (KERNEL_BASE + (KERNEL_SIZE - 1) - lastRangeEnd >= size) {
spot = lastRangeEnd;
args->virtual_allocated_range[last_valloc_entry].size += size;
goto out;
}
// see if there's a gap before the first one
if (args->virtual_allocated_range[0].start > KERNEL_BASE) {
if (args->virtual_allocated_range[0].start - KERNEL_BASE >= size) {
args->virtual_allocated_range[0].start -= size;
spot = args->virtual_allocated_range[0].start;
goto out;
}
}
}
out:
return spot;
}
static bool
is_page_in_physical_memory_range(kernel_args *args, addr_t address)
{
// TODO: horrible brute-force method of determining if the page can be allocated
for (uint32 i = 0; i < args->num_physical_memory_ranges; i++) {
if (address >= args->physical_memory_range[i].start
&& address < args->physical_memory_range[i].start
+ args->physical_memory_range[i].size)
return true;
}
return false;
}
static addr_t
allocate_early_physical_page(kernel_args *args)
{
for (uint32 i = 0; i < args->num_physical_allocated_ranges; i++) {
addr_t nextPage;
nextPage = args->physical_allocated_range[i].start
+ args->physical_allocated_range[i].size;
// see if the page after the next allocated paddr run can be allocated
if (i + 1 < args->num_physical_allocated_ranges
&& args->physical_allocated_range[i + 1].size != 0) {
// see if the next page will collide with the next allocated range
if (nextPage >= args->physical_allocated_range[i+1].start)
continue;
}
// see if the next physical page fits in the memory block
if (is_page_in_physical_memory_range(args, nextPage)) {
// we got one!
args->physical_allocated_range[i].size += B_PAGE_SIZE;
return nextPage / B_PAGE_SIZE;
}
}
return 0;
// could not allocate a block
}
/*!
This one uses the kernel_args' physical and virtual memory ranges to
allocate some pages before the VM is completely up.
*/
addr_t
vm_allocate_early(kernel_args *args, size_t virtualSize, size_t physicalSize,
uint32 attributes)
{
if (physicalSize > virtualSize)
physicalSize = virtualSize;
// find the vaddr to allocate at
addr_t virtualBase = allocate_early_virtual(args, virtualSize);
//dprintf("vm_allocate_early: vaddr 0x%lx\n", virtualAddress);
// map the pages
for (uint32 i = 0; i < PAGE_ALIGN(physicalSize) / B_PAGE_SIZE; i++) {
addr_t physicalAddress = allocate_early_physical_page(args);
if (physicalAddress == 0)
panic("error allocating early page!\n");
//dprintf("vm_allocate_early: paddr 0x%lx\n", physicalAddress);
arch_vm_translation_map_early_map(args, virtualBase + i * B_PAGE_SIZE,
physicalAddress * B_PAGE_SIZE, attributes,
&allocate_early_physical_page);
}
return virtualBase;
}
status_t
vm_init(kernel_args *args)
{
struct preloaded_image *image;
void *address;
status_t err = 0;
uint32 i;
TRACE(("vm_init: entry\n"));
err = arch_vm_translation_map_init(args);
err = arch_vm_init(args);
// initialize some globals
sNextAreaID = 1;
sAreaHashLock = -1;
sAvailableMemoryLock.sem = -1;
vm_page_init_num_pages(args);
sAvailableMemory = vm_page_num_pages() * B_PAGE_SIZE;
// reduce the heap size if we have not so much RAM
size_t heapSize = HEAP_SIZE;
if (sAvailableMemory < 100 * 1024 * 1024)
heapSize /= 4;
else if (sAvailableMemory < 200 * 1024 * 1024)
heapSize /= 2;
// map in the new heap and initialize it
addr_t heapBase = vm_allocate_early(args, heapSize, heapSize,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
TRACE(("heap at 0x%lx\n", heapBase));
heap_init(heapBase, heapSize);
vm_low_memory_init();
size_t slabInitialSize = 2 * B_PAGE_SIZE;
addr_t slabInitialBase = vm_allocate_early(args, slabInitialSize,
slabInitialSize, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
slab_init(args, slabInitialBase, slabInitialSize);
// initialize the free page list and physical page mapper
vm_page_init(args);
// initialize the hash table that stores the pages mapped to caches
vm_cache_init(args);
{
vm_area *area;
sAreaHash = hash_init(REGION_HASH_TABLE_SIZE, (addr_t)&area->hash_next - (addr_t)area,
&area_compare, &area_hash);
if (sAreaHash == NULL)
panic("vm_init: error creating aspace hash table\n");
}
vm_address_space_init();
reserve_boot_loader_ranges(args);
// do any further initialization that the architecture dependant layers may need now
arch_vm_translation_map_init_post_area(args);
arch_vm_init_post_area(args);
vm_page_init_post_area(args);
// allocate areas to represent stuff that already exists
address = (void *)ROUNDOWN(heapBase, B_PAGE_SIZE);
create_area("kernel heap", &address, B_EXACT_ADDRESS, heapSize,
B_ALREADY_WIRED, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
address = (void *)ROUNDOWN(slabInitialBase, B_PAGE_SIZE);
create_area("initial slab space", &address, B_EXACT_ADDRESS,
slabInitialSize, B_ALREADY_WIRED, B_KERNEL_READ_AREA
| B_KERNEL_WRITE_AREA);
allocate_kernel_args(args);
args->kernel_image.name = "kernel";
// the lazy boot loader currently doesn't set the kernel's name...
create_preloaded_image_areas(&args->kernel_image);
// allocate areas for preloaded images
for (image = args->preloaded_images; image != NULL; image = image->next) {
create_preloaded_image_areas(image);
}
// allocate kernel stacks
for (i = 0; i < args->num_cpus; i++) {
char name[64];
sprintf(name, "idle thread %lu kstack", i + 1);
address = (void *)args->cpu_kstack[i].start;
create_area(name, &address, B_EXACT_ADDRESS, args->cpu_kstack[i].size,
B_ALREADY_WIRED, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
}
// add some debugger commands
add_debugger_command("areas", &dump_area_list, "Dump a list of all areas");
add_debugger_command("area", &dump_area, "Dump info about a particular area");
add_debugger_command("cache", &dump_cache, "Dump vm_cache");
add_debugger_command("cache_tree", &dump_cache_tree, "Dump vm_cache tree");
#if DEBUG_CACHE_LIST
add_debugger_command("caches", &dump_caches, "List vm_cache structures");
#endif
add_debugger_command("avail", &dump_available_memory, "Dump available memory");
add_debugger_command("dl", &display_mem, "dump memory long words (64-bit)");
add_debugger_command("dw", &display_mem, "dump memory words (32-bit)");
add_debugger_command("ds", &display_mem, "dump memory shorts (16-bit)");
add_debugger_command("db", &display_mem, "dump memory bytes (8-bit)");
TRACE(("vm_init: exit\n"));
return err;
}
status_t
vm_init_post_sem(kernel_args *args)
{
vm_area *area;
// This frees all unused boot loader resources and makes its space available again
arch_vm_init_end(args);
unreserve_boot_loader_ranges(args);
// fill in all of the semaphores that were not allocated before
// since we're still single threaded and only the kernel address space exists,
// it isn't that hard to find all of the ones we need to create
benaphore_init(&sAvailableMemoryLock, "available memory lock");
arch_vm_translation_map_init_post_sem(args);
vm_address_space_init_post_sem();
for (area = vm_kernel_address_space()->areas; area;
area = area->address_space_next) {
if (area->id == RESERVED_AREA_ID)
continue;
if (area->cache->lock.sem < 0)
mutex_init(&area->cache->lock, "vm_cache");
}
sAreaHashLock = create_sem(WRITE_COUNT, "area hash");
mutex_init(&sAreaCacheLock, "area->cache");
mutex_init(&sMappingLock, "page mappings");
slab_init_post_sem();
return heap_init_post_sem(args);
}
status_t
vm_init_post_thread(kernel_args *args)
{
vm_page_init_post_thread(args);
vm_daemon_init();
vm_low_memory_init_post_thread();
return heap_init_post_thread(args);
}
status_t
vm_init_post_modules(kernel_args *args)
{
return arch_vm_init_post_modules(args);
}
void
permit_page_faults(void)
{
struct thread *thread = thread_get_current_thread();
if (thread != NULL)
atomic_add(&thread->page_faults_allowed, 1);
}
void
forbid_page_faults(void)
{
struct thread *thread = thread_get_current_thread();
if (thread != NULL)
atomic_add(&thread->page_faults_allowed, -1);
}
status_t
vm_page_fault(addr_t address, addr_t faultAddress, bool isWrite, bool isUser,
addr_t *newIP)
{
FTRACE(("vm_page_fault: page fault at 0x%lx, ip 0x%lx\n", address, faultAddress));
*newIP = 0;
status_t status = vm_soft_fault(address, isWrite, isUser);
if (status < B_OK) {
dprintf("vm_page_fault: vm_soft_fault returned error '%s' on fault at 0x%lx, ip 0x%lx, write %d, user %d, thread 0x%lx\n",
strerror(status), address, faultAddress, isWrite, isUser,
thread_get_current_thread_id());
if (!isUser) {
struct thread *thread = thread_get_current_thread();
if (thread != NULL && thread->fault_handler != 0) {
// this will cause the arch dependant page fault handler to
// modify the IP on the interrupt frame or whatever to return
// to this address
*newIP = thread->fault_handler;
} else {
// unhandled page fault in the kernel
panic("vm_page_fault: unhandled page fault in kernel space at 0x%lx, ip 0x%lx\n",
address, faultAddress);
}
} else {
#if 1
// ToDo: remove me once we have proper userland debugging support (and tools)
vm_address_space *addressSpace = vm_get_current_user_address_space();
vm_area *area;
acquire_sem_etc(addressSpace->sem, READ_COUNT, 0, 0);
area = vm_area_lookup(addressSpace, faultAddress);
dprintf("vm_page_fault: sending team \"%s\" 0x%lx SIGSEGV, ip %#lx (\"%s\" +%#lx)\n",
thread_get_current_thread()->team->name,
thread_get_current_thread()->team->id, faultAddress,
area ? area->name : "???", faultAddress - (area ? area->base : 0x0));
// We can print a stack trace of the userland thread here.
#if 1
if (area) {
struct stack_frame {
#if defined(__INTEL__) || defined(__POWERPC__) || defined(__M68K__)
struct stack_frame* previous;
void* return_address;
#else
// ...
#warning writeme
#endif
} frame;
#ifdef __INTEL__
struct iframe *iframe = i386_get_user_iframe();
if (iframe == NULL)
panic("iframe is NULL!");
status_t status = user_memcpy(&frame, (void *)iframe->ebp,
sizeof(struct stack_frame));
#elif defined(__POWERPC__)
struct iframe *iframe = ppc_get_user_iframe();
if (iframe == NULL)
panic("iframe is NULL!");
status_t status = user_memcpy(&frame, (void *)iframe->r1,
sizeof(struct stack_frame));
#else
# warning "vm_page_fault() stack trace won't work"
status = B_ERROR;
#endif
dprintf("stack trace:\n");
while (status == B_OK && frame.return_address != NULL) {
dprintf(" %p", frame.return_address);
area = vm_area_lookup(addressSpace,
(addr_t)frame.return_address);
if (area) {
dprintf(" (%s + %#lx)", area->name,
(addr_t)frame.return_address - area->base);
}
dprintf("\n");
status = user_memcpy(&frame, frame.previous,
sizeof(struct stack_frame));
}
}
#endif // 0 (stack trace)
release_sem_etc(addressSpace->sem, READ_COUNT, 0);
vm_put_address_space(addressSpace);
#endif
if (user_debug_exception_occurred(B_SEGMENT_VIOLATION, SIGSEGV))
send_signal(team_get_current_team_id(), SIGSEGV);
}
}
return B_HANDLED_INTERRUPT;
}
static inline status_t
fault_acquire_locked_source(vm_cache *cache, vm_cache **_source)
{
retry:
vm_cache *source = cache->source;
if (source == NULL)
return B_ERROR;
if (source->busy)
return B_BUSY;
vm_cache_acquire_ref(source);
mutex_lock(&source->lock);
if (source->busy) {
mutex_unlock(&source->lock);
vm_cache_release_ref(source);
goto retry;
}
*_source = source;
return B_OK;
}
/*!
Inserts a busy dummy page into a cache, and makes sure the cache won't go
away by grabbing a reference to it.
*/
static inline void
fault_insert_dummy_page(vm_cache *cache, vm_dummy_page &dummyPage,
off_t cacheOffset)
{
dummyPage.state = PAGE_STATE_BUSY;
vm_cache_acquire_ref(cache);
vm_cache_insert_page(cache, &dummyPage, cacheOffset);
dummyPage.busy_condition.Publish(&dummyPage, "page");
}
/*!
Removes the busy dummy page from a cache, and releases its reference to
the cache.
*/
static inline void
fault_remove_dummy_page(vm_dummy_page &dummyPage, bool isLocked)
{
vm_cache *cache = dummyPage.cache;
if (!isLocked)
mutex_lock(&cache->lock);
if (dummyPage.state == PAGE_STATE_BUSY) {
vm_cache_remove_page(cache, &dummyPage);
dummyPage.state = PAGE_STATE_INACTIVE;
dummyPage.busy_condition.Unpublish();
}
if (!isLocked)
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
}
/*!
Finds a page at the specified \a cacheOffset in either the \a topCacheRef
or in its source chain. Will also page in a missing page in case there is
a cache that has the page.
If it couldn't find a page, it will return the vm_cache that should get it,
otherwise, it will return the vm_cache that contains the cache.
It always grabs a reference to the vm_cache that it returns, and also locks it.
*/
static inline status_t
fault_find_page(vm_translation_map *map, vm_cache *topCache,
off_t cacheOffset, bool isWrite, vm_dummy_page &dummyPage,
vm_cache **_pageCache, vm_page** _page, bool* _restart)
{
*_restart = false;
vm_cache *cache = topCache;
vm_cache *lastCache = NULL;
vm_page *page = NULL;
vm_cache_acquire_ref(cache);
mutex_lock(&cache->lock);
// we release this later in the loop
while (cache != NULL) {
if (lastCache != NULL)
vm_cache_release_ref(lastCache);
// we hold the lock of the cache at this point
lastCache = cache;
for (;;) {
page = vm_cache_lookup_page(cache, cacheOffset);
if (page != NULL && page->state != PAGE_STATE_BUSY) {
// we found the page
break;
}
if (page == NULL || page == &dummyPage)
break;
// page must be busy -- wait for it to become unbusy
{
ConditionVariableEntry<vm_page> entry;
entry.Add(page);
mutex_unlock(&cache->lock);
entry.Wait();
mutex_lock(&cache->lock);
}
if (cache->busy) {
// The cache became busy, which means, it is about to be
// removed by vm_cache_remove_consumer(). We start again with
// the top cache.
ConditionVariableEntry<vm_cache> entry;
entry.Add(cache);
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
entry.Wait();
*_restart = true;
return B_OK;
}
}
if (page != NULL && page != &dummyPage)
break;
// The current cache does not contain the page we're looking for
// see if the vm_store has it
vm_store *store = cache->store;
if (store->ops->has_page != NULL
&& store->ops->has_page(store, cacheOffset)) {
// insert a fresh page and mark it busy -- we're going to read it in
page = vm_page_allocate_page(PAGE_STATE_FREE, true);
vm_cache_insert_page(cache, page, cacheOffset);
ConditionVariable<vm_page> busyCondition;
busyCondition.Publish(page, "page");
mutex_unlock(&cache->lock);
// get a virtual address for the page
iovec vec;
map->ops->get_physical_page(
page->physical_page_number * B_PAGE_SIZE,
(addr_t *)&vec.iov_base, PHYSICAL_PAGE_CAN_WAIT);
size_t bytesRead = vec.iov_len = B_PAGE_SIZE;
// read it in
status_t status = store->ops->read(store, cacheOffset, &vec, 1,
&bytesRead, false);
map->ops->put_physical_page((addr_t)vec.iov_base);
mutex_lock(&cache->lock);
if (status < B_OK) {
// on error remove and free the page
dprintf("reading page from store %p (cache %p) returned: %s!\n",
store, cache, strerror(status));
busyCondition.Unpublish();
vm_cache_remove_page(cache, page);
vm_page_set_state(page, PAGE_STATE_FREE);
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
return status;
}
// mark the page unbusy again
page->state = PAGE_STATE_ACTIVE;
busyCondition.Unpublish();
break;
}
// If we're at the top most cache, insert the dummy page here to keep
// other threads from faulting on the same address and chasing us up the
// cache chain
if (cache == topCache && dummyPage.state != PAGE_STATE_BUSY)
fault_insert_dummy_page(cache, dummyPage, cacheOffset);
vm_cache *nextCache;
status_t status = fault_acquire_locked_source(cache, &nextCache);
if (status == B_BUSY) {
// the source cache is currently in the process of being merged
// with his only consumer (cacheRef); since its pages are moved
// upwards, too, we try this cache again
mutex_unlock(&cache->lock);
thread_yield();
mutex_lock(&cache->lock);
if (cache->busy) {
// The cache became busy, which means, it is about to be
// removed by vm_cache_remove_consumer(). We start again with
// the top cache.
ConditionVariableEntry<vm_cache> entry;
entry.Add(cache);
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
entry.Wait();
*_restart = true;
return B_OK;
}
lastCache = NULL;
continue;
} else if (status < B_OK)
nextCache = NULL;
mutex_unlock(&cache->lock);
// at this point, we still hold a ref to this cache (through lastCacheRef)
cache = nextCache;
}
if (page == NULL) {
// there was no adequate page, determine the cache for a clean one
if (cache == NULL) {
// We rolled off the end of the cache chain, so we need to decide which
// cache will get the new page we're about to create.
cache = isWrite ? topCache : lastCache;
// Read-only pages come in the deepest cache - only the
// top most cache may have direct write access.
vm_cache_acquire_ref(cache);
mutex_lock(&cache->lock);
if (cache->busy) {
// The cache became busy, which means, it is about to be
// removed by vm_cache_remove_consumer(). We start again with
// the top cache.
ConditionVariableEntry<vm_cache> entry;
entry.Add(cache);
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
entry.Wait();
*_restart = true;
} else {
vm_page* newPage = vm_cache_lookup_page(cache, cacheOffset);
if (newPage && newPage != &dummyPage) {
// A new page turned up. It could be the one we're looking
// for, but it could as well be a dummy page from someone
// else or an otherwise busy page. We can't really handle
// that here. Hence we completely restart this functions.
mutex_unlock(&cache->lock);
vm_cache_release_ref(cache);
*_restart = true;
}
}
}
// release the reference of the last vm_cache we still have from the loop above
if (lastCache != NULL)
vm_cache_release_ref(lastCache);
} else {
// we still own a reference to the cache
}
*_pageCache = cache;
*_page = page;
return B_OK;
}
/*!
Returns the page that should be mapped into the area that got the fault.
It returns the owner of the page in \a sourceCache - it keeps a reference
to it, and has also locked it on exit.
*/
static inline status_t
fault_get_page(vm_translation_map *map, vm_cache *topCache, off_t cacheOffset,
bool isWrite, vm_dummy_page &dummyPage, vm_cache **_sourceCache,
vm_cache **_copiedSource, vm_page** _page)
{
vm_cache *cache;
vm_page *page;
bool restart;
for (;;) {
status_t status = fault_find_page(map, topCache, cacheOffset, isWrite,
dummyPage, &cache, &page, &restart);
if (status != B_OK)
return status;
if (!restart)
break;
// Remove the dummy page, if it has been inserted.
mutex_lock(&topCache->lock);
if (dummyPage.state == PAGE_STATE_BUSY) {
ASSERT_PRINT(dummyPage.cache == topCache, "dummy page: %p\n",
&dummyPage);
fault_remove_dummy_page(dummyPage, true);
}
mutex_unlock(&topCache->lock);
}
if (page == NULL) {
// we still haven't found a page, so we allocate a clean one
page = vm_page_allocate_page(PAGE_STATE_CLEAR, true);
FTRACE(("vm_soft_fault: just allocated page 0x%lx\n", page->physical_page_number));
// Insert the new page into our cache, and replace it with the dummy page if necessary
// If we inserted a dummy page into this cache (i.e. if it is the top
// cache), we have to remove it now
if (dummyPage.state == PAGE_STATE_BUSY && dummyPage.cache == cache) {
#ifdef DEBUG_PAGE_CACHE_TRANSITIONS
page->debug_flags = dummyPage.debug_flags | 0x8;
if (dummyPage.collided_page != NULL) {
dummyPage.collided_page->collided_page = page;
page->collided_page = dummyPage.collided_page;
}
#endif // DEBUG_PAGE_CACHE_TRANSITIONS
fault_remove_dummy_page(dummyPage, true);
}
vm_cache_insert_page(cache, page, cacheOffset);
if (dummyPage.state == PAGE_STATE_BUSY) {
#ifdef DEBUG_PAGE_CACHE_TRANSITIONS
page->debug_flags = dummyPage.debug_flags | 0x10;
if (dummyPage.collided_page != NULL) {
dummyPage.collided_page->collided_page = page;
page->collided_page = dummyPage.collided_page;
}
#endif // DEBUG_PAGE_CACHE_TRANSITIONS
// This is not the top cache into which we inserted the dummy page,
// let's remove it from there. We need to temporarily unlock our
// cache to comply with the cache locking policy.
mutex_unlock(&cache->lock);
fault_remove_dummy_page(dummyPage, false);
mutex_lock(&cache->lock);
}
}
// We now have the page and a cache it belongs to - we now need to make
// sure that the area's cache can access it, too, and sees the correct data
if (page->cache != topCache && isWrite) {
// Now we have a page that has the data we want, but in the wrong cache
// object so we need to copy it and stick it into the top cache.
// Note that this and the "if" before are mutual exclusive. If
// fault_find_page() didn't find the page, it would return the top cache
// for write faults.
vm_page *sourcePage = page;
void *source, *dest;
// ToDo: if memory is low, it might be a good idea to steal the page
// from our source cache - if possible, that is
FTRACE(("get new page, copy it, and put it into the topmost cache\n"));
page = vm_page_allocate_page(PAGE_STATE_FREE, true);
#if 0
if (cacheOffset == 0x12000)
dprintf("%ld: copy page %p to page %p from cache %p to cache %p\n", find_thread(NULL),
sourcePage, page, sourcePage->cache, topCacheRef->cache);
#endif
// try to get a mapping for the src and dest page so we can copy it
for (;;) {
map->ops->get_physical_page(sourcePage->physical_page_number * B_PAGE_SIZE,
(addr_t *)&source, PHYSICAL_PAGE_CAN_WAIT);
if (map->ops->get_physical_page(page->physical_page_number * B_PAGE_SIZE,
(addr_t *)&dest, PHYSICAL_PAGE_NO_WAIT) == B_OK)
break;
// it couldn't map the second one, so sleep and retry
// keeps an extremely rare deadlock from occuring
map->ops->put_physical_page((addr_t)source);
snooze(5000);
}
memcpy(dest, source, B_PAGE_SIZE);
map->ops->put_physical_page((addr_t)source);
map->ops->put_physical_page((addr_t)dest);
if (sourcePage->state != PAGE_STATE_MODIFIED)
vm_page_set_state(sourcePage, PAGE_STATE_ACTIVE);
mutex_unlock(&cache->lock);
mutex_lock(&topCache->lock);
// Since the top cache has been unlocked for a while, someone else
// (vm_cache_remove_consumer()) might have replaced our dummy page.
vm_page* newPage = NULL;
for (;;) {
newPage = vm_cache_lookup_page(topCache, cacheOffset);
if (newPage == NULL || newPage == &dummyPage) {
newPage = NULL;
break;
}
if (newPage->state != PAGE_STATE_BUSY)
break;
// The page is busy, wait till it becomes unbusy.
ConditionVariableEntry<vm_page> entry;
entry.Add(newPage);
mutex_unlock(&topCache->lock);
entry.Wait();
mutex_lock(&topCache->lock);
}
if (newPage) {
// Indeed someone else threw in a page. We free ours and are happy.
vm_page_set_state(page, PAGE_STATE_FREE);
page = newPage;
} else {
// Insert the new page into our cache and remove the dummy page, if
// necessary.
// if we inserted a dummy page into this cache, we have to remove it now
if (dummyPage.state == PAGE_STATE_BUSY) {
ASSERT_PRINT(dummyPage.cache == topCache, "dummy page: %p\n",
&dummyPage);
fault_remove_dummy_page(dummyPage, true);
}
vm_cache_insert_page(topCache, page, cacheOffset);
}
*_copiedSource = cache;
cache = topCache;
vm_cache_acquire_ref(cache);
}
*_sourceCache = cache;
*_page = page;
return B_OK;
}
static status_t
vm_soft_fault(addr_t originalAddress, bool isWrite, bool isUser)
{
vm_address_space *addressSpace;
FTRACE(("vm_soft_fault: thid 0x%lx address 0x%lx, isWrite %d, isUser %d\n",
thread_get_current_thread_id(), originalAddress, isWrite, isUser));
addr_t address = ROUNDOWN(originalAddress, B_PAGE_SIZE);
if (IS_KERNEL_ADDRESS(address)) {
addressSpace = vm_get_kernel_address_space();
} else if (IS_USER_ADDRESS(address)) {
addressSpace = vm_get_current_user_address_space();
if (addressSpace == NULL) {
if (!isUser) {
dprintf("vm_soft_fault: kernel thread accessing invalid user memory!\n");
return B_BAD_ADDRESS;
} else {
// XXX weird state.
panic("vm_soft_fault: non kernel thread accessing user memory that doesn't exist!\n");
}
}
} else {
// the hit was probably in the 64k DMZ between kernel and user space
// this keeps a user space thread from passing a buffer that crosses
// into kernel space
return B_BAD_ADDRESS;
}
AddressSpaceReadLocker locker(addressSpace);
atomic_add(&addressSpace->fault_count, 1);
// Get the area the fault was in
vm_area *area = vm_area_lookup(addressSpace, address);
if (area == NULL) {
dprintf("vm_soft_fault: va 0x%lx not covered by area in address space\n",
originalAddress);
return B_BAD_ADDRESS;
}
// check permissions
if (isUser && (area->protection & B_USER_PROTECTION) == 0) {
dprintf("user access on kernel area 0x%lx at %p\n", area->id, (void *)originalAddress);
return B_PERMISSION_DENIED;
}
if (isWrite && (area->protection & (B_WRITE_AREA | (isUser ? 0 : B_KERNEL_WRITE_AREA))) == 0) {
dprintf("write access attempted on read-only area 0x%lx at %p\n",
area->id, (void *)originalAddress);
return B_PERMISSION_DENIED;
}
// We have the area, it was a valid access, so let's try to resolve the page fault now.
// At first, the top most cache from the area is investigated
vm_cache *topCache = vm_area_get_locked_cache(area);
off_t cacheOffset = address - area->base + area->cache_offset;
int32 changeCount = addressSpace->change_count;
atomic_add(&area->no_cache_change, 1);
// make sure the area's cache isn't replaced during the page fault
// See if this cache has a fault handler - this will do all the work for us
{
vm_store *store = topCache->store;
if (store->ops->fault != NULL) {
// Note, since the page fault is resolved with interrupts enabled, the
// fault handler could be called more than once for the same reason -
// the store must take this into account
status_t status = store->ops->fault(store, addressSpace, cacheOffset);
if (status != B_BAD_HANDLER) {
vm_area_put_locked_cache(topCache);
return status;
}
}
}
mutex_unlock(&topCache->lock);
// The top most cache has no fault handler, so let's see if the cache or its sources
// already have the page we're searching for (we're going from top to bottom)
vm_translation_map *map = &addressSpace->translation_map;
size_t reservePages = 2 + map->ops->map_max_pages_need(map,
originalAddress, originalAddress);
vm_page_reserve_pages(reservePages);
// we may need up to 2 pages - reserving them upfront makes sure
// we don't have any cache locked, so that the page daemon/thief
// can do their job without problems
vm_dummy_page dummyPage;
dummyPage.cache = NULL;
dummyPage.state = PAGE_STATE_INACTIVE;
dummyPage.type = PAGE_TYPE_DUMMY;
dummyPage.wired_count = 0;
#ifdef DEBUG_PAGE_CACHE_TRANSITIONS
dummyPage.debug_flags = 0;
dummyPage.collided_page = NULL;
#endif // DEBUG_PAGE_CACHE_TRANSITIONS
vm_cache *copiedPageSource = NULL;
vm_cache *pageSource;
vm_page *page;
// TODO: We keep the address space read lock during the whole operation
// which might be rather expensive depending on where the data has to
// be retrieved from.
status_t status = fault_get_page(map, topCache, cacheOffset, isWrite,
dummyPage, &pageSource, &copiedPageSource, &page);
if (status == B_OK) {
// All went fine, all there is left to do is to map the page into the address space
// In case this is a copy-on-write page, we need to unmap it from the area now
if (isWrite && page->cache == topCache)
vm_unmap_pages(area, address, B_PAGE_SIZE, true);
// TODO: there is currently no mechanism to prevent a page being mapped
// more than once in case of a second page fault!
// If the page doesn't reside in the area's cache, we need to make sure it's
// mapped in read-only, so that we cannot overwrite someone else's data (copy-on-write)
uint32 newProtection = area->protection;
if (page->cache != topCache && !isWrite)
newProtection &= ~(B_WRITE_AREA | B_KERNEL_WRITE_AREA);
vm_map_page(area, page, address, newProtection);
mutex_unlock(&pageSource->lock);
vm_cache_release_ref(pageSource);
}
atomic_add(&area->no_cache_change, -1);
if (copiedPageSource)
vm_cache_release_ref(copiedPageSource);
if (dummyPage.state == PAGE_STATE_BUSY) {
// We still have the dummy page in the cache - that happens if we didn't need
// to allocate a new page before, but could use one in another cache
fault_remove_dummy_page(dummyPage, false);
}
vm_cache_release_ref(topCache);
vm_page_unreserve_pages(reservePages);
return status;
}
/*! You must have the address space's sem held */
vm_area *
vm_area_lookup(vm_address_space *addressSpace, addr_t address)
{
vm_area *area;
// check the areas list first
area = addressSpace->area_hint;
if (area && area->base <= address && area->base + (area->size - 1) >= address)
goto found;
for (area = addressSpace->areas; area != NULL; area = area->address_space_next) {
if (area->id == RESERVED_AREA_ID)
continue;
if (area->base <= address && area->base + (area->size - 1) >= address)
break;
}
found:
if (area)
addressSpace->area_hint = area;
return area;
}
status_t
vm_get_physical_page(addr_t paddr, addr_t *_vaddr, uint32 flags)
{
return (*vm_kernel_address_space()->translation_map.ops->get_physical_page)(paddr, _vaddr, flags);
}
status_t
vm_put_physical_page(addr_t vaddr)
{
return (*vm_kernel_address_space()->translation_map.ops->put_physical_page)(vaddr);
}
void
vm_unreserve_memory(size_t amount)
{
benaphore_lock(&sAvailableMemoryLock);
sAvailableMemory += amount;
benaphore_unlock(&sAvailableMemoryLock);
}
status_t
vm_try_reserve_memory(size_t amount)
{
status_t status;
benaphore_lock(&sAvailableMemoryLock);
//dprintf("try to reserve %lu bytes, %Lu left\n", amount, sAvailableMemory);
if (sAvailableMemory > amount) {
sAvailableMemory -= amount;
status = B_OK;
} else
status = B_NO_MEMORY;
benaphore_unlock(&sAvailableMemoryLock);
return status;
}
status_t
vm_set_area_memory_type(area_id id, addr_t physicalBase, uint32 type)
{
AddressSpaceReadLocker locker;
vm_area *area;
status_t status = locker.SetFromArea(id, area);
if (status != B_OK)
return status;
return arch_vm_set_memory_type(area, physicalBase, type);
}
/** This function enforces some protection properties:
* - if B_WRITE_AREA is set, B_WRITE_KERNEL_AREA is set as well
* - if only B_READ_AREA has been set, B_KERNEL_READ_AREA is also set
* - if no protection is specified, it defaults to B_KERNEL_READ_AREA
* and B_KERNEL_WRITE_AREA.
*/
static void
fix_protection(uint32 *protection)
{
if ((*protection & B_KERNEL_PROTECTION) == 0) {
if ((*protection & B_USER_PROTECTION) == 0
|| (*protection & B_WRITE_AREA) != 0)
*protection |= B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA;
else
*protection |= B_KERNEL_READ_AREA;
}
}
static void
fill_area_info(struct vm_area *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 = B_FULL_LOCK;
info->team = area->address_space->id;
info->copy_count = 0;
info->in_count = 0;
info->out_count = 0;
// ToDo: retrieve real values here!
vm_cache *cache = vm_area_get_locked_cache(area);
// Note, this is a simplification; the cache could be larger than this area
info->ram_size = cache->page_count * B_PAGE_SIZE;
vm_area_put_locked_cache(cache);
}
/*!
Tests wether or not the area that contains the specified address
needs any kind of locking, and actually exists.
Used by both lock_memory() and unlock_memory().
*/
static status_t
test_lock_memory(vm_address_space *addressSpace, addr_t address,
bool &needsLocking)
{
acquire_sem_etc(addressSpace->sem, READ_COUNT, 0, 0);
vm_area *area = vm_area_lookup(addressSpace, address);
if (area != NULL) {
// This determines if we need to lock the memory at all
needsLocking = area->cache_type != CACHE_TYPE_NULL
&& area->cache_type != CACHE_TYPE_DEVICE
&& area->wiring != B_FULL_LOCK
&& area->wiring != B_CONTIGUOUS;
}
release_sem_etc(addressSpace->sem, READ_COUNT, 0);
if (area == NULL)
return B_BAD_ADDRESS;
return B_OK;
}
// #pragma mark - kernel public API
status_t
user_memcpy(void *to, const void *from, size_t size)
{
if (arch_cpu_user_memcpy(to, from, size, &thread_get_current_thread()->fault_handler) < B_OK)
return B_BAD_ADDRESS;
return B_OK;
}
/** \brief Copies at most (\a size - 1) characters from the string in \a from to
* the string in \a to, NULL-terminating the result.
*
* \param to Pointer to the destination C-string.
* \param from Pointer to the source C-string.
* \param size Size in bytes of the string buffer pointed to by \a to.
*
* \return strlen(\a from).
*/
ssize_t
user_strlcpy(char *to, const char *from, size_t size)
{
return arch_cpu_user_strlcpy(to, from, size, &thread_get_current_thread()->fault_handler);
}
status_t
user_memset(void *s, char c, size_t count)
{
if (arch_cpu_user_memset(s, c, count, &thread_get_current_thread()->fault_handler) < B_OK)
return B_BAD_ADDRESS;
return B_OK;
}
long
lock_memory(void *address, ulong numBytes, ulong flags)
{
vm_address_space *addressSpace = NULL;
struct vm_translation_map *map;
addr_t unalignedBase = (addr_t)address;
addr_t end = unalignedBase + numBytes;
addr_t base = ROUNDOWN(unalignedBase, B_PAGE_SIZE);
bool isUser = IS_USER_ADDRESS(address);
bool needsLocking = true;
if (isUser)
addressSpace = vm_get_current_user_address_space();
else
addressSpace = vm_get_kernel_address_space();
if (addressSpace == NULL)
return B_ERROR;
// test if we're on an area that allows faults at all
map = &addressSpace->translation_map;
status_t status = test_lock_memory(addressSpace, base, needsLocking);
if (status < B_OK)
goto out;
if (!needsLocking)
goto out;
for (; base < end; base += B_PAGE_SIZE) {
addr_t physicalAddress;
uint32 protection;
status_t status;
map->ops->lock(map);
status = map->ops->query(map, base, &physicalAddress, &protection);
map->ops->unlock(map);
if (status < B_OK)
goto out;
if ((protection & PAGE_PRESENT) != 0) {
// if B_READ_DEVICE is set, the caller intents to write to the locked
// memory, so if it hasn't been mapped writable, we'll try the soft
// fault anyway
if ((flags & B_READ_DEVICE) == 0
|| (protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0) {
// update wiring
vm_page *page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL)
panic("couldn't lookup physical page just allocated\n");
page->wired_count++;
// TODO: needs to be atomic on all platforms!
continue;
}
}
status = vm_soft_fault(base, (flags & B_READ_DEVICE) != 0, isUser);
if (status != B_OK) {
dprintf("lock_memory(address = %p, numBytes = %lu, flags = %lu) failed: %s\n",
(void *)unalignedBase, numBytes, flags, strerror(status));
goto out;
}
map->ops->lock(map);
status = map->ops->query(map, base, &physicalAddress, &protection);
map->ops->unlock(map);
if (status < B_OK)
goto out;
// update wiring
vm_page *page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL)
panic("couldn't lookup physical page");
page->wired_count++;
// TODO: needs to be atomic on all platforms!
}
out:
vm_put_address_space(addressSpace);
return status;
}
long
unlock_memory(void *address, ulong numBytes, ulong flags)
{
vm_address_space *addressSpace = NULL;
struct vm_translation_map *map;
addr_t unalignedBase = (addr_t)address;
addr_t end = unalignedBase + numBytes;
addr_t base = ROUNDOWN(unalignedBase, B_PAGE_SIZE);
bool needsLocking = true;
if (IS_USER_ADDRESS(address))
addressSpace = vm_get_current_user_address_space();
else
addressSpace = vm_get_kernel_address_space();
if (addressSpace == NULL)
return B_ERROR;
map = &addressSpace->translation_map;
status_t status = test_lock_memory(addressSpace, base, needsLocking);
if (status < B_OK)
goto out;
if (!needsLocking)
goto out;
for (; base < end; base += B_PAGE_SIZE) {
map->ops->lock(map);
addr_t physicalAddress;
uint32 protection;
status = map->ops->query(map, base, &physicalAddress,
&protection);
map->ops->unlock(map);
if (status < B_OK)
goto out;
if ((protection & PAGE_PRESENT) == 0)
panic("calling unlock_memory() on unmapped memory!");
// update wiring
vm_page *page = vm_lookup_page(physicalAddress / B_PAGE_SIZE);
if (page == NULL)
panic("couldn't lookup physical page");
page->wired_count--;
// TODO: needs to be atomic on all platforms!
}
out:
vm_put_address_space(addressSpace);
return status;
}
/** According to the BeBook, this function should always succeed.
* This is no longer the case.
*/
long
get_memory_map(const void *address, ulong numBytes, physical_entry *table,
long numEntries)
{
vm_address_space *addressSpace;
addr_t virtualAddress = (addr_t)address;
addr_t pageOffset = virtualAddress & (B_PAGE_SIZE - 1);
addr_t physicalAddress;
status_t status = B_OK;
int32 index = -1;
addr_t offset = 0;
bool interrupts = are_interrupts_enabled();
TRACE(("get_memory_map(%p, %lu bytes, %ld entries)\n", address, numBytes,
numEntries));
if (numEntries == 0 || numBytes == 0)
return B_BAD_VALUE;
// in which address space is the address to be found?
if (IS_USER_ADDRESS(virtualAddress))
addressSpace = thread_get_current_thread()->team->address_space;
else
addressSpace = vm_kernel_address_space();
if (addressSpace == NULL)
return B_ERROR;
vm_translation_map *map = &addressSpace->translation_map;
if (interrupts)
map->ops->lock(map);
while (offset < numBytes) {
addr_t bytes = min_c(numBytes - offset, B_PAGE_SIZE);
uint32 flags;
if (interrupts) {
status = map->ops->query(map, (addr_t)address + offset,
&physicalAddress, &flags);
} else {
status = map->ops->query_interrupt(map, (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;
}
// need to switch to the next physical_entry?
if (index < 0 || (addr_t)table[index].address
!= physicalAddress - table[index].size) {
if (++index + 1 > numEntries) {
// table to small
status = B_BUFFER_OVERFLOW;
break;
}
table[index].address = (void *)physicalAddress;
table[index].size = bytes;
} else {
// page does fit in current entry
table[index].size += bytes;
}
offset += bytes;
}
if (interrupts)
map->ops->unlock(map);
// close the entry list
if (status == B_OK) {
// if it's only one entry, we will silently accept the missing ending
if (numEntries == 1)
return B_OK;
if (++index + 1 > numEntries)
return B_BUFFER_OVERFLOW;
table[index].address = NULL;
table[index].size = 0;
}
return status;
}
area_id
area_for(void *address)
{
team_id space;
if (IS_USER_ADDRESS(address)) {
// we try the user team address space, if any
space = vm_current_user_address_space_id();
if (space < B_OK)
return space;
} else
space = vm_kernel_address_space_id();
return vm_area_for(space, (addr_t)address);
}
area_id
find_area(const char *name)
{
acquire_sem_etc(sAreaHashLock, READ_COUNT, 0, 0);
struct hash_iterator iterator;
hash_open(sAreaHash, &iterator);
vm_area *area;
area_id id = B_NAME_NOT_FOUND;
while ((area = (vm_area *)hash_next(sAreaHash, &iterator)) != NULL) {
if (area->id == RESERVED_AREA_ID)
continue;
if (!strcmp(area->name, name)) {
id = area->id;
break;
}
}
hash_close(sAreaHash, &iterator, false);
release_sem_etc(sAreaHashLock, READ_COUNT, 0);
return id;
}
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;
vm_area *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, int32 *cookie, area_info *info, size_t size)
{
addr_t nextBase = *(addr_t *)cookie;
// we're already through the list
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;
vm_area *area;
for (area = locker.AddressSpace()->areas; area != NULL;
area = area->address_space_next) {
if (area->id == RESERVED_AREA_ID)
continue;
if (area->base > nextBase)
break;
}
if (area == NULL) {
nextBase = (addr_t)-1;
return B_ENTRY_NOT_FOUND;
}
fill_area_info(area, info, size);
*cookie = (int32)(area->base);
return B_OK;
}
status_t
set_area_protection(area_id area, uint32 newProtection)
{
fix_protection(&newProtection);
return vm_set_area_protection(vm_kernel_address_space_id(), area,
newProtection);
}
status_t
resize_area(area_id areaID, size_t newSize)
{
// is newSize a multiple of B_PAGE_SIZE?
if (newSize & (B_PAGE_SIZE - 1))
return B_BAD_VALUE;
// lock all affected address spaces and the cache
vm_area* area;
vm_cache* cache;
MultiAddressSpaceLocker locker;
status_t status = locker.AddAreaCacheAndLock(areaID, true, true, area,
&cache);
if (status != B_OK)
return status;
AreaCacheLocker cacheLocker(cache); // already locked
size_t oldSize = area->size;
if (newSize == oldSize)
return B_OK;
// Resize all areas of this area's cache
if (cache->type != CACHE_TYPE_RAM)
return B_NOT_ALLOWED;
if (oldSize < newSize) {
// We need to check if all areas of this cache can be resized
for (vm_area* current = cache->areas; current != NULL;
current = current->cache_next) {
if (current->address_space_next
&& current->address_space_next->base <= (current->base
+ newSize)) {
// If the area was created inside a reserved area, it can
// also be resized in that area
// ToDo: if there is free space after the reserved area, it could be used as well...
vm_area *next = current->address_space_next;
if (next->id == RESERVED_AREA_ID
&& next->cache_offset <= current->base
&& next->base - 1 + next->size >= current->base - 1 + newSize)
continue;
return B_ERROR;
}
}
}
// Okay, looks good so far, so let's do it
for (vm_area* current = cache->areas; current != NULL;
current = current->cache_next) {
if (current->address_space_next
&& current->address_space_next->base <= (current->base + newSize)) {
vm_area *next = current->address_space_next;
if (next->id == RESERVED_AREA_ID
&& next->cache_offset <= current->base
&& next->base - 1 + next->size >= current->base - 1 + newSize) {
// resize reserved area
addr_t offset = current->base + newSize - next->base;
if (next->size <= offset) {
current->address_space_next = next->address_space_next;
free(next);
} else {
next->size -= offset;
next->base += offset;
}
} else {
status = B_ERROR;
break;
}
}
current->size = newSize;
// we also need to unmap all pages beyond the new size, if the area has shrinked
if (newSize < oldSize) {
vm_unmap_pages(current, current->base + newSize, oldSize - newSize,
false);
}
}
if (status == B_OK)
status = vm_cache_resize(cache, newSize);
if (status < B_OK) {
// This shouldn't really be possible, but hey, who knows
for (vm_area* current = cache->areas; current != NULL;
current = current->cache_next) {
current->size = oldSize;
}
}
// ToDo: we must honour the lock restrictions of this area
return status;
}
/** Transfers the specified area to a new team. The caller must be the owner
* of the area (not yet enforced but probably should be).
* This function is currently not exported to the kernel namespace, but is
* only accessible using the _kern_transfer_area() syscall.
*/
static status_t
transfer_area(area_id id, void **_address, uint32 addressSpec, team_id target)
{
// TODO: implement like clone_area(), just atomically (ie. hand out a new area ID)!
return B_ERROR;
#if 0
vm_address_space *sourceAddressSpace;
vm_address_space *targetAddressSpace;
void *reservedAddress = NULL;
vm_area *reserved;
vm_area *area = vm_get_area(id);
if (area == NULL)
return B_BAD_VALUE;
// ToDo: check if the current team owns the area
status_t status = team_get_address_space(target, &targetAddressSpace);
if (status != B_OK)
goto err1;
// We will first remove the area, and then reserve its former
// address range so that we can later reclaim it if the
// transfer failed.
sourceAddressSpace = area->address_space;
reserved = create_reserved_area_struct(sourceAddressSpace, 0);
if (reserved == NULL) {
status = B_NO_MEMORY;
goto err2;
}
acquire_sem_etc(sourceAddressSpace->sem, WRITE_COUNT, 0, 0);
// unmap the area in the source address space
vm_unmap_pages(area, area->base, area->size);
// TODO: there might be additional page faults at this point!
reservedAddress = (void *)area->base;
remove_area_from_address_space(sourceAddressSpace, area);
status = insert_area(sourceAddressSpace, &reservedAddress, B_EXACT_ADDRESS,
area->size, reserved);
// famous last words: this cannot fail :)
release_sem_etc(sourceAddressSpace->sem, WRITE_COUNT, 0);
if (status != B_OK)
goto err3;
// insert the area into the target address space
acquire_sem_etc(targetAddressSpace->sem, WRITE_COUNT, 0, 0);
// check to see if this address space has entered DELETE state
if (targetAddressSpace->state == VM_ASPACE_STATE_DELETION) {
// okay, someone is trying to delete this adress space now, so we can't
// insert the area, so back out
status = B_BAD_TEAM_ID;
goto err4;
}
status = insert_area(targetAddressSpace, _address, addressSpec, area->size, area);
if (status < B_OK)
goto err4;
// The area was successfully transferred to the new team when we got here
area->address_space = targetAddressSpace;
// TODO: take area lock/wiring into account!
release_sem_etc(targetAddressSpace->sem, WRITE_COUNT, 0);
vm_unreserve_address_range(sourceAddressSpace->id, reservedAddress,
area->size);
vm_put_address_space(sourceAddressSpace);
// we keep the reference of the target address space for the
// area, so we only have to put the one from the source
vm_put_area(area);
return B_OK;
err4:
release_sem_etc(targetAddressSpace->sem, WRITE_COUNT, 0);
err3:
// insert the area again into the source address space
acquire_sem_etc(sourceAddressSpace->sem, WRITE_COUNT, 0, 0);
// check to see if this address space has entered DELETE state
if (sourceAddressSpace->state == VM_ASPACE_STATE_DELETION
|| insert_area(sourceAddressSpace, &reservedAddress, B_EXACT_ADDRESS,
area->size, area) != B_OK) {
// We can't insert the area anymore - we have to delete it manually
vm_cache *cache = vm_area_get_locked_cache(area);
atomic_add(&area->no_cache_change, 1);
vm_area_put_locked_cache(cache);
vm_cache_remove_area(cache, area);
vm_cache_release_ref(cache);
free(area->name);
free(area);
area = NULL;
}
release_sem_etc(sourceAddressSpace->sem, WRITE_COUNT, 0);
err2:
vm_put_address_space(targetAddressSpace);
err1:
if (area != NULL)
vm_put_area(area);
return status;
#endif
}
area_id
map_physical_memory(const char *name, void *physicalAddress, size_t numBytes,
uint32 addressSpec, uint32 protection, void **_virtualAddress)
{
if (!arch_vm_supports_protection(protection))
return B_NOT_SUPPORTED;
fix_protection(&protection);
return vm_map_physical_memory(vm_kernel_address_space_id(), name, _virtualAddress,
addressSpec, numBytes, protection, (addr_t)physicalAddress);
}
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(vm_kernel_address_space_id(), name, _address,
addressSpec, protection, REGION_NO_PRIVATE_MAP, source);
}
area_id
create_area_etc(struct team *team, const char *name, void **address, uint32 addressSpec,
uint32 size, uint32 lock, uint32 protection)
{
fix_protection(&protection);
return vm_create_anonymous_area(team->id, (char *)name, address,
addressSpec, size, lock, protection);
}
area_id
create_area(const char *name, void **_address, uint32 addressSpec, size_t size, uint32 lock,
uint32 protection)
{
fix_protection(&protection);
return vm_create_anonymous_area(vm_kernel_address_space_id(), (char *)name, _address,
addressSpec, size, lock, protection);
}
status_t
delete_area_etc(struct team *team, area_id area)
{
return vm_delete_area(team->id, area);
}
status_t
delete_area(area_id area)
{
return vm_delete_area(vm_kernel_address_space_id(), area);
}
// #pragma mark - Userland syscalls
status_t
_user_reserve_heap_address_range(addr_t* userAddress, uint32 addressSpec, addr_t size)
{
// filter out some unavailable values (for userland)
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(vm_current_user_address_space_id(),
(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(vm_current_user_address_space_id(),
(void *)address, size);
return B_BAD_ADDRESS;
}
return B_OK;
}
area_id
_user_area_for(void *address)
{
return vm_area_for(vm_current_user_address_space_id(), (addr_t)address);
}
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;
// TODO: do we want to prevent userland from seeing kernel protections?
//info.protection &= B_USER_PROTECTION;
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, int32 *userCookie, area_info *userInfo)
{
int32 cookie;
if (!IS_USER_ADDRESS(userCookie)
|| !IS_USER_ADDRESS(userInfo)
|| user_memcpy(&cookie, userCookie, sizeof(int32)) < 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;
//info.protection &= B_USER_PROTECTION;
if (user_memcpy(userCookie, &cookie, sizeof(int32)) < 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) != 0)
return B_BAD_VALUE;
fix_protection(&newProtection);
return vm_set_area_protection(vm_current_user_address_space_id(), area,
newProtection);
}
status_t
_user_resize_area(area_id area, size_t newSize)
{
// ToDo: Since we restrict deleting of areas to those owned by the team,
// we should also do that for resizing (check other functions, too).
return resize_area(area, newSize);
}
status_t
_user_transfer_area(area_id area, void **userAddress, uint32 addressSpec, team_id target)
{
// filter out some unavailable values (for userland)
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;
status_t status = transfer_area(area, &address, addressSpec, target);
if (status < B_OK)
return status;
if (user_memcpy(userAddress, &address, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
return status;
}
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;
// filter out some unavailable values (for userland)
switch (addressSpec) {
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
return B_BAD_VALUE;
}
if ((protection & ~B_USER_PROTECTION) != 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;
fix_protection(&protection);
area_id clonedArea = vm_clone_area(vm_current_user_address_space_id(), name, &address,
addressSpec, protection, REGION_NO_PRIVATE_MAP, sourceArea);
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;
// filter out some unavailable values (for userland)
switch (addressSpec) {
case B_ANY_KERNEL_ADDRESS:
case B_ANY_KERNEL_BLOCK_ADDRESS:
return B_BAD_VALUE;
}
if ((protection & ~B_USER_PROTECTION) != 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;
fix_protection(&protection);
area_id area = vm_create_anonymous_area(vm_current_user_address_space_id(),
(char *)name, &address, addressSpec, size, lock, protection);
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)
{
// Unlike the BeOS implementation, you can now only delete areas
// that you have created yourself from userland.
// The documentation to delete_area() explicetly states that this
// will be restricted in the future, and so it will.
return vm_delete_area(vm_current_user_address_space_id(), area);
}
// ToDo: create a BeOS style call for this!
area_id
_user_vm_map_file(const char *userName, void **userAddress, int addressSpec,
addr_t size, int protection, int mapping, const char *userPath, off_t offset)
{
char name[B_OS_NAME_LENGTH];
char path[B_PATH_NAME_LENGTH];
void *address;
area_id area;
if (!IS_USER_ADDRESS(userName) || !IS_USER_ADDRESS(userAddress)
|| !IS_USER_ADDRESS(userPath)
|| user_strlcpy(name, userName, B_OS_NAME_LENGTH) < B_OK
|| user_strlcpy(path, userPath, B_PATH_NAME_LENGTH) < B_OK
|| user_memcpy(&address, userAddress, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
// userland created areas can always be accessed by the kernel
protection |= B_KERNEL_READ_AREA | (protection & B_WRITE_AREA ? B_KERNEL_WRITE_AREA : 0);
area = _vm_map_file(vm_current_user_address_space_id(), name, &address,
addressSpec, size, protection, mapping, path, offset, false);
if (area < B_OK)
return area;
if (user_memcpy(userAddress, &address, sizeof(address)) < B_OK)
return B_BAD_ADDRESS;
return area;
}