1758 lines
49 KiB
C
1758 lines
49 KiB
C
|
/* Copyright (C) 2005,2006 David Decotigny
|
||
|
|
||
|
This program is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU General Public License
|
||
|
as published by the Free Software Foundation; either version 2
|
||
|
of the License, or (at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
||
|
USA.
|
||
|
*/
|
||
|
|
||
|
#include <sos/assert.h>
|
||
|
#include <sos/list.h>
|
||
|
#include <sos/physmem.h>
|
||
|
#include <sos/kmem_slab.h>
|
||
|
#include <drivers/bochs.h>
|
||
|
#include <hwcore/mm_context.h>
|
||
|
#include <hwcore/paging.h>
|
||
|
#include <hwcore/irq.h>
|
||
|
#include <drivers/zero.h>
|
||
|
|
||
|
#include "umem_vmm.h"
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_as
|
||
|
{
|
||
|
/** The process that owns this address space */
|
||
|
struct sos_process * process;
|
||
|
|
||
|
/** The MMU configuration of this address space */
|
||
|
struct sos_mm_context * mm_context;
|
||
|
|
||
|
/** The list of VRs in this address space */
|
||
|
struct sos_umem_vmm_vr * list_vr;
|
||
|
|
||
|
/** Heap location */
|
||
|
sos_uaddr_t heap_start;
|
||
|
sos_size_t heap_size; /**< Updated by sos_umem_vmm_brk() */
|
||
|
|
||
|
/* Memory usage statistics */
|
||
|
sos_size_t phys_total; /* shared + private */
|
||
|
struct vm_usage
|
||
|
{
|
||
|
sos_size_t overall;
|
||
|
sos_size_t ro, rw, code /* all: non readable, read and read/write */;
|
||
|
} vm_total, vm_shrd;
|
||
|
|
||
|
/* Page fault counters */
|
||
|
sos_size_t pgflt_cow;
|
||
|
sos_size_t pgflt_page_in;
|
||
|
sos_size_t pgflt_invalid;
|
||
|
};
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_vr
|
||
|
{
|
||
|
/** The address space owning this VR */
|
||
|
struct sos_umem_vmm_as *address_space;
|
||
|
|
||
|
/** The location of the mapping in user space */
|
||
|
sos_uaddr_t start;
|
||
|
sos_size_t size;
|
||
|
|
||
|
/** What accesses are allowed (read, write, exec): @see
|
||
|
SOS_VM_MAP_PROT_* flags in hwcore/paging.h */
|
||
|
sos_ui32_t access_rights;
|
||
|
|
||
|
/** Flags of the VR. Allowed flags:
|
||
|
* - SOS_VR_MAP_SHARED
|
||
|
*/
|
||
|
sos_ui32_t flags;
|
||
|
|
||
|
/**
|
||
|
* The callbacks for the VR called along map/unmapping of the
|
||
|
* resource
|
||
|
*/
|
||
|
struct sos_umem_vmm_vr_ops *ops;
|
||
|
|
||
|
/** Description of the resource being mapped, if any */
|
||
|
struct sos_umem_vmm_mapped_resource *mapped_resource;
|
||
|
sos_luoffset_t offset_in_resource;
|
||
|
|
||
|
/** The VRs of an AS are linked together and are accessible by way
|
||
|
of as->list_vr */
|
||
|
struct sos_umem_vmm_vr *prev_in_as, *next_in_as;
|
||
|
|
||
|
/** The VRs mapping a given resource are linked together and are
|
||
|
accessible by way of mapped_resource->list_vr */
|
||
|
struct sos_umem_vmm_vr *prev_in_mapped_resource, *next_in_mapped_resource;
|
||
|
};
|
||
|
|
||
|
|
||
|
/*
|
||
|
* We use special slab caches to allocate AS and VR data structures
|
||
|
*/
|
||
|
static struct sos_kslab_cache * cache_of_as;
|
||
|
static struct sos_kslab_cache * cache_of_vr;
|
||
|
|
||
|
|
||
|
/** Temporary function to debug: list the VRs of the given As */
|
||
|
void sos_dump_as(const struct sos_umem_vmm_as * as, const char *str)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr *vr;
|
||
|
int nb_vr;
|
||
|
|
||
|
sos_bochs_printf("AS %p - %s:\n", as, str);
|
||
|
sos_bochs_printf(" physical mem: %x\n",
|
||
|
as->phys_total);
|
||
|
sos_bochs_printf(" VM (all/ro+rw/exec) tot:%x/%x+%x/%x shrd:%x/%x+%x/%x\n",
|
||
|
as->vm_total.overall,
|
||
|
as->vm_total.ro, as->vm_total.rw, as->vm_total.code,
|
||
|
as->vm_shrd.overall,
|
||
|
as->vm_shrd.ro, as->vm_shrd.rw, as->vm_shrd.code);
|
||
|
sos_bochs_printf(" pgflt cow=%d pgin=%d inv=%d\n",
|
||
|
as->pgflt_cow, as->pgflt_page_in, as->pgflt_invalid);
|
||
|
list_foreach_named(as->list_vr, vr, nb_vr, prev_in_as, next_in_as)
|
||
|
{
|
||
|
sos_bochs_printf(" VR[%d]=%x: [%x,%x[ (sz=%x) mr=(%x)+%llx %c%c%c fl=%x\n",
|
||
|
nb_vr, (unsigned)vr,
|
||
|
vr->start, vr->start + vr->size, vr->size,
|
||
|
(unsigned)vr->mapped_resource,
|
||
|
vr->offset_in_resource,
|
||
|
(vr->access_rights & SOS_VM_MAP_PROT_READ)?'r':'-',
|
||
|
(vr->access_rights & SOS_VM_MAP_PROT_WRITE)?'w':'-',
|
||
|
(vr->access_rights & SOS_VM_MAP_PROT_EXEC)?'x':'-',
|
||
|
(unsigned)vr->flags);
|
||
|
}
|
||
|
sos_bochs_printf("FIN (%s)\n", str);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Physical address of THE page (full of 0s) used for anonymous
|
||
|
* mappings
|
||
|
*/
|
||
|
sos_paddr_t sos_zero_physpage = 0 /* Initial value prior to allocation */;
|
||
|
sos_vaddr_t sos_zero_kernelpage = 0 /* Initial value prior to allocation */;
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Helper functions defined at the bottom of the file
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Helper function to retrieve the first VR to have a vr->end >= uaddr
|
||
|
*/
|
||
|
static struct sos_umem_vmm_vr *
|
||
|
find_enclosing_or_next_vr(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t uaddr);
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Helper function to retrieve the first VR that overlaps the given
|
||
|
* interval, if any
|
||
|
*/
|
||
|
static struct sos_umem_vmm_vr *
|
||
|
find_first_intersecting_vr(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t start_uaddr, sos_size_t size);
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Helper function to find first address where there is enough
|
||
|
* space. Begin to look for such an interval at or after the given
|
||
|
* address
|
||
|
*
|
||
|
* @param hint_addr The address where to begin the scan, or NULL
|
||
|
*/
|
||
|
static sos_uaddr_t
|
||
|
find_first_free_interval(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t hint_uaddr, sos_size_t size);
|
||
|
|
||
|
|
||
|
/** Called each time a VR of the AS changes. Don't cope with any
|
||
|
underlying physcal mapping/unmapping, COW, etc... */
|
||
|
static void
|
||
|
as_account_change_of_vr_protection(struct sos_umem_vmm_as * as,
|
||
|
sos_bool_t is_shared,
|
||
|
sos_size_t size,
|
||
|
sos_ui32_t prev_access_rights,
|
||
|
sos_ui32_t new_access_rights);
|
||
|
|
||
|
|
||
|
sos_ret_t sos_umem_vmm_subsystem_setup()
|
||
|
{
|
||
|
/* Allocate a new kernel physical page mapped into kernel space and
|
||
|
reset it with 0s */
|
||
|
sos_zero_kernelpage = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP);
|
||
|
if (sos_zero_kernelpage == (sos_vaddr_t)NULL)
|
||
|
return -SOS_ENOMEM;
|
||
|
memset((void*)sos_zero_kernelpage, 0x0, SOS_PAGE_SIZE);
|
||
|
|
||
|
/* Keep a reference to the underlying pphysical page... */
|
||
|
sos_zero_physpage = sos_paging_get_paddr(sos_zero_kernelpage);
|
||
|
SOS_ASSERT_FATAL(NULL != (void*)sos_zero_physpage);
|
||
|
sos_physmem_ref_physpage_at(sos_zero_physpage);
|
||
|
|
||
|
/* Allocate the VR/AS caches */
|
||
|
cache_of_as
|
||
|
= sos_kmem_cache_create("Address space structures",
|
||
|
sizeof(struct sos_umem_vmm_as),
|
||
|
1, 0,
|
||
|
SOS_KSLAB_CREATE_MAP
|
||
|
| SOS_KSLAB_CREATE_ZERO);
|
||
|
if (! cache_of_as)
|
||
|
{
|
||
|
sos_physmem_unref_physpage(sos_zero_physpage);
|
||
|
return -SOS_ENOMEM;
|
||
|
}
|
||
|
|
||
|
cache_of_vr
|
||
|
= sos_kmem_cache_create("Virtual Region structures",
|
||
|
sizeof(struct sos_umem_vmm_vr),
|
||
|
1, 0,
|
||
|
SOS_KSLAB_CREATE_MAP
|
||
|
| SOS_KSLAB_CREATE_ZERO);
|
||
|
if (! cache_of_vr)
|
||
|
{
|
||
|
sos_physmem_unref_physpage(sos_zero_physpage);
|
||
|
sos_kmem_cache_destroy(cache_of_as);
|
||
|
return -SOS_ENOMEM;
|
||
|
}
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
static struct sos_umem_vmm_as * current_address_space = NULL;
|
||
|
struct sos_umem_vmm_as * sos_umem_vmm_get_current_as(void)
|
||
|
{
|
||
|
return current_address_space;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_umem_vmm_set_current_as(struct sos_umem_vmm_as * as)
|
||
|
{
|
||
|
sos_ui32_t flags;
|
||
|
struct sos_umem_vmm_as *prev_as = current_address_space;
|
||
|
|
||
|
if (current_address_space == as)
|
||
|
return SOS_OK;
|
||
|
|
||
|
if (NULL != as)
|
||
|
{
|
||
|
sos_disable_IRQs(flags);
|
||
|
sos_process_ref(sos_umem_vmm_get_process(as));
|
||
|
sos_mm_context_switch_to(sos_umem_vmm_get_mm_context(as));
|
||
|
current_address_space = as;
|
||
|
sos_restore_IRQs(flags);
|
||
|
}
|
||
|
else
|
||
|
current_address_space = as;
|
||
|
|
||
|
if (prev_as)
|
||
|
sos_process_unref(sos_umem_vmm_get_process(prev_as));
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_as *
|
||
|
sos_umem_vmm_create_empty_as(struct sos_process *owner)
|
||
|
{
|
||
|
struct sos_umem_vmm_as * as
|
||
|
= (struct sos_umem_vmm_as *) sos_kmem_cache_alloc(cache_of_as, 0);
|
||
|
if (! as)
|
||
|
return NULL;
|
||
|
|
||
|
as->mm_context = sos_mm_context_create();
|
||
|
if (NULL == as->mm_context)
|
||
|
{
|
||
|
/* Error */
|
||
|
sos_kmem_cache_free((sos_vaddr_t)as);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
as->process = owner;
|
||
|
return as;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_as *
|
||
|
sos_umem_vmm_duplicate_as(struct sos_umem_vmm_as * model_as,
|
||
|
struct sos_process *for_owner)
|
||
|
{
|
||
|
__label__ undo_creation;
|
||
|
struct sos_umem_vmm_vr * model_vr;
|
||
|
int nb_vr;
|
||
|
|
||
|
struct sos_umem_vmm_as * new_as
|
||
|
= (struct sos_umem_vmm_as *) sos_kmem_cache_alloc(cache_of_as, 0);
|
||
|
if (! new_as)
|
||
|
return NULL;
|
||
|
|
||
|
new_as->process = for_owner;
|
||
|
list_init_named(new_as->list_vr, prev_in_as, next_in_as);
|
||
|
|
||
|
/*
|
||
|
* Switch to the current threads' mm_context, as duplicating it implies
|
||
|
* being able to configure some of its mappings as read-only (for
|
||
|
* COW)
|
||
|
*/
|
||
|
SOS_ASSERT_FATAL(SOS_OK
|
||
|
== sos_thread_prepare_user_space_access(model_as,
|
||
|
(sos_vaddr_t)
|
||
|
NULL));
|
||
|
|
||
|
/* Copy the virtual regions */
|
||
|
list_foreach_named(model_as->list_vr, model_vr, nb_vr, prev_in_as, next_in_as)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr * vr;
|
||
|
|
||
|
/* Prepare COW on the read/write private mappings */
|
||
|
if ( !(model_vr->flags & SOS_VR_MAP_SHARED)
|
||
|
&& (model_vr->access_rights & SOS_VM_MAP_PROT_WRITE) )
|
||
|
{
|
||
|
/* Mark the underlying physical pages (if any) as
|
||
|
read-only */
|
||
|
SOS_ASSERT_FATAL(SOS_OK
|
||
|
== sos_paging_prepare_COW(model_vr->start,
|
||
|
model_vr->size));
|
||
|
}
|
||
|
|
||
|
/* Allocate a new virtual region and copy the 'model' into it */
|
||
|
vr = (struct sos_umem_vmm_vr *) sos_kmem_cache_alloc(cache_of_vr, 0);
|
||
|
if (! vr)
|
||
|
goto undo_creation;
|
||
|
memcpy(vr, model_vr, sizeof(*vr));
|
||
|
vr->address_space = new_as;
|
||
|
|
||
|
/* Signal the "new" mapping to the underlying VR mapper */
|
||
|
if (vr->ops && vr->ops->ref)
|
||
|
vr->ops->ref(vr);
|
||
|
|
||
|
/* Insert the new VR into the new AS */
|
||
|
list_add_tail_named(new_as->list_vr, vr, prev_in_as, next_in_as);
|
||
|
|
||
|
/* Insert the new VR into the list of mappings of the resource */
|
||
|
list_add_tail_named(model_vr->mapped_resource->list_vr, vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
}
|
||
|
|
||
|
/* Now copy the current MMU configuration */
|
||
|
new_as->mm_context = sos_mm_context_duplicate(model_as->mm_context);
|
||
|
if (NULL == new_as->mm_context)
|
||
|
goto undo_creation;
|
||
|
|
||
|
/* Correct behavior */
|
||
|
new_as->heap_start = model_as->heap_start;
|
||
|
new_as->heap_size = model_as->heap_size;
|
||
|
new_as->phys_total = model_as->phys_total;
|
||
|
memcpy(& new_as->vm_total, & model_as->vm_total, sizeof(struct vm_usage));
|
||
|
memcpy(& new_as->vm_shrd, & model_as->vm_shrd, sizeof(struct vm_usage));
|
||
|
SOS_ASSERT_FATAL(SOS_OK == sos_thread_end_user_space_access());
|
||
|
return new_as;
|
||
|
|
||
|
/* Handle erroneous behavior */
|
||
|
undo_creation:
|
||
|
SOS_ASSERT_FATAL(SOS_OK == sos_thread_end_user_space_access());
|
||
|
sos_umem_vmm_delete_as(new_as);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_delete_as(struct sos_umem_vmm_as * as)
|
||
|
{
|
||
|
while(! list_is_empty_named(as->list_vr, prev_in_as, next_in_as))
|
||
|
{
|
||
|
struct sos_umem_vmm_vr * vr;
|
||
|
vr = list_get_head_named(as->list_vr, prev_in_as, next_in_as);
|
||
|
|
||
|
/* Remove the vr from the lists */
|
||
|
list_pop_head_named(as->list_vr, prev_in_as, next_in_as);
|
||
|
list_delete_named(vr->mapped_resource->list_vr, vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
|
||
|
/* Signal to the underlying VR mapper that the mapping is
|
||
|
suppressed */
|
||
|
if (vr->ops)
|
||
|
{
|
||
|
if (vr->ops->unmap)
|
||
|
vr->ops->unmap(vr, vr->start, vr->size);
|
||
|
if (vr->ops->unref)
|
||
|
vr->ops->unref(vr);
|
||
|
}
|
||
|
|
||
|
sos_kmem_cache_free((sos_vaddr_t)vr);
|
||
|
}
|
||
|
|
||
|
/* Release MMU configuration */
|
||
|
if (as->mm_context)
|
||
|
sos_mm_context_unref(as->mm_context);
|
||
|
|
||
|
/* Now unallocate main address space construct */
|
||
|
sos_kmem_cache_free((sos_vaddr_t)as);
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_process *
|
||
|
sos_umem_vmm_get_process(struct sos_umem_vmm_as * as)
|
||
|
{
|
||
|
return as->process;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_mm_context *
|
||
|
sos_umem_vmm_get_mm_context(struct sos_umem_vmm_as * as)
|
||
|
{
|
||
|
return as->mm_context;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_vr *
|
||
|
sos_umem_vmm_get_vr_at_address(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t uaddr)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr * vr;
|
||
|
vr = find_enclosing_or_next_vr(as, uaddr);
|
||
|
if (! vr)
|
||
|
return NULL;
|
||
|
|
||
|
/* Ok uaddr <= vr->end, but do we have uaddr > vr->start ? */
|
||
|
if (uaddr < vr->start)
|
||
|
return NULL;
|
||
|
|
||
|
return vr;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_as *
|
||
|
sos_umem_vmm_get_as_of_vr(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->address_space;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_vr_ops *
|
||
|
sos_umem_vmm_get_ops_of_vr(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->ops;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ui32_t sos_umem_vmm_get_prot_of_vr(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->access_rights;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ui32_t sos_umem_vmm_get_flags_of_vr(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->flags;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sos_umem_vmm_mapped_resource *
|
||
|
sos_umem_vmm_get_mapped_resource_of_vr(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->mapped_resource;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_uaddr_t sos_umem_vmm_get_start_of_vr(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->start;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_size_t sos_umem_vmm_get_size_of_vr(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->size;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_luoffset_t sos_umem_vmm_get_offset_in_resource(struct sos_umem_vmm_vr * vr)
|
||
|
{
|
||
|
return vr->offset_in_resource;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_set_ops_of_vr(struct sos_umem_vmm_vr * vr,
|
||
|
struct sos_umem_vmm_vr_ops * ops)
|
||
|
{
|
||
|
/* Don't allow to overwrite any preceding VR ops */
|
||
|
SOS_ASSERT_FATAL(NULL == vr->ops);
|
||
|
|
||
|
vr->ops = ops;
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* When resize asks to map the resource elsewhere, make sure not to
|
||
|
* overwrite the offset_in_resource field
|
||
|
*/
|
||
|
#define INTERNAL_MAP_CALLED_FROM_MREMAP (1 << 8)
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_map(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t * /*in/out*/uaddr, sos_size_t size,
|
||
|
sos_ui32_t access_rights,
|
||
|
sos_ui32_t flags,
|
||
|
struct sos_umem_vmm_mapped_resource * resource,
|
||
|
sos_luoffset_t offset_in_resource)
|
||
|
{
|
||
|
__label__ return_mmap;
|
||
|
sos_uaddr_t hint_uaddr;
|
||
|
struct sos_umem_vmm_vr *prev_vr, *next_vr, *vr, *preallocated_vr;
|
||
|
sos_bool_t merge_with_preceding, merge_with_next, used_preallocated_vr;
|
||
|
sos_bool_t internal_map_called_from_mremap
|
||
|
= (flags & INTERNAL_MAP_CALLED_FROM_MREMAP);
|
||
|
|
||
|
sos_ret_t retval = SOS_OK;
|
||
|
used_preallocated_vr = FALSE;
|
||
|
hint_uaddr = *uaddr;
|
||
|
|
||
|
/* Default mapping address is NULL */
|
||
|
*uaddr = (sos_vaddr_t)NULL;
|
||
|
|
||
|
if (! resource)
|
||
|
return -SOS_EINVAL;
|
||
|
if (! resource->mmap)
|
||
|
return -SOS_EPERM;
|
||
|
|
||
|
if (! SOS_IS_PAGE_ALIGNED(hint_uaddr))
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
if (size <= 0)
|
||
|
return -SOS_EINVAL;
|
||
|
size = SOS_PAGE_ALIGN_SUP(size);
|
||
|
|
||
|
if (flags & SOS_VR_MAP_SHARED)
|
||
|
{
|
||
|
/* Make sure the mapped resource allows the required protection flags */
|
||
|
if ( ( (access_rights & SOS_VM_MAP_PROT_READ)
|
||
|
&& !(resource->allowed_access_rights & SOS_VM_MAP_PROT_READ) )
|
||
|
|| ( (access_rights & SOS_VM_MAP_PROT_WRITE)
|
||
|
&& !(resource->allowed_access_rights & SOS_VM_MAP_PROT_WRITE) )
|
||
|
|| ( (access_rights & SOS_VM_MAP_PROT_EXEC)
|
||
|
&& !(resource->allowed_access_rights & SOS_VM_MAP_PROT_EXEC)) )
|
||
|
return -SOS_EPERM;
|
||
|
}
|
||
|
|
||
|
/* Sanity checks over the offset_in_resource parameter */
|
||
|
if ( !internal_map_called_from_mremap
|
||
|
&& ( resource->flags & SOS_MAPPED_RESOURCE_ANONYMOUS ) )
|
||
|
/* Initial offset ignored for anonymous mappings */
|
||
|
{
|
||
|
/* Nothing to check */
|
||
|
}
|
||
|
|
||
|
/* Make sure that the offset in resource won't overflow */
|
||
|
else if (offset_in_resource + size <= offset_in_resource)
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Filter out unsupported flags */
|
||
|
access_rights &= (SOS_VM_MAP_PROT_READ
|
||
|
| SOS_VM_MAP_PROT_WRITE
|
||
|
| SOS_VM_MAP_PROT_EXEC);
|
||
|
flags &= (SOS_VR_MAP_SHARED
|
||
|
| SOS_VR_MAP_FIXED);
|
||
|
|
||
|
/* Pre-allocate a new VR. Because once we found a valid slot inside
|
||
|
the VR list, we don't want the list to be altered by another
|
||
|
process */
|
||
|
preallocated_vr
|
||
|
= (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0);
|
||
|
if (! preallocated_vr)
|
||
|
return -SOS_ENOMEM;
|
||
|
|
||
|
/* Compute the user address of the new mapping */
|
||
|
if (flags & SOS_VR_MAP_FIXED)
|
||
|
{
|
||
|
/*
|
||
|
* The address is imposed
|
||
|
*/
|
||
|
|
||
|
/* Make sure the hint_uaddr hint is valid */
|
||
|
if (! SOS_PAGING_IS_USER_AREA(hint_uaddr, size) )
|
||
|
{ retval = -SOS_EINVAL; goto return_mmap; }
|
||
|
|
||
|
/* Unmap any overlapped VR */
|
||
|
retval = sos_umem_vmm_unmap(as, hint_uaddr, size);
|
||
|
if (SOS_OK != retval)
|
||
|
{ goto return_mmap; }
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/*
|
||
|
* A free range has to be determined
|
||
|
*/
|
||
|
|
||
|
/* Find a suitable free VR */
|
||
|
hint_uaddr = find_first_free_interval(as, hint_uaddr, size);
|
||
|
if (! hint_uaddr)
|
||
|
{ retval = -SOS_ENOMEM; goto return_mmap; }
|
||
|
}
|
||
|
|
||
|
/* For anonymous resource mappings, set the initial
|
||
|
offset_in_resource to the initial virtual start address in user
|
||
|
space */
|
||
|
if ( !internal_map_called_from_mremap
|
||
|
&& (resource->flags & SOS_MAPPED_RESOURCE_ANONYMOUS ) )
|
||
|
offset_in_resource = hint_uaddr;
|
||
|
|
||
|
/* Lookup next and previous VR, if any. This will allow us to merge
|
||
|
the regions, when possible */
|
||
|
next_vr = find_enclosing_or_next_vr(as, hint_uaddr);
|
||
|
if (next_vr)
|
||
|
{
|
||
|
/* Find previous VR, if any */
|
||
|
prev_vr = next_vr->prev_in_as;
|
||
|
/* The list is curcular: it may happen that we looped over the
|
||
|
tail of the list (ie the list is a singleton) */
|
||
|
if (prev_vr->start > hint_uaddr)
|
||
|
prev_vr = NULL; /* No preceding VR */
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Otherwise we went beyond the last VR */
|
||
|
prev_vr = list_get_tail_named(as->list_vr, prev_in_as, next_in_as);
|
||
|
}
|
||
|
|
||
|
/* Merge with preceding VR ? */
|
||
|
merge_with_preceding
|
||
|
= ( (NULL != prev_vr)
|
||
|
&& (prev_vr->mapped_resource == resource)
|
||
|
&& (prev_vr->offset_in_resource + prev_vr->size == offset_in_resource)
|
||
|
&& (prev_vr->start + prev_vr->size == hint_uaddr)
|
||
|
&& (prev_vr->flags == flags)
|
||
|
&& (prev_vr->access_rights == access_rights) );
|
||
|
|
||
|
/* Merge with next VR ? */
|
||
|
merge_with_next
|
||
|
= ( (NULL != next_vr)
|
||
|
&& (next_vr->mapped_resource == resource)
|
||
|
&& (offset_in_resource + size == next_vr->offset_in_resource)
|
||
|
&& (hint_uaddr + size == next_vr->start)
|
||
|
&& (next_vr->flags == flags)
|
||
|
&& (next_vr->access_rights == access_rights) );
|
||
|
|
||
|
if (merge_with_preceding && merge_with_next)
|
||
|
{
|
||
|
/* Widen the prev_vr VR to encompass both the new VR and the next_vr */
|
||
|
vr = prev_vr;
|
||
|
vr->size += size + next_vr->size;
|
||
|
|
||
|
/* Remove the next_vr VR */
|
||
|
list_delete_named(as->list_vr, next_vr, prev_in_as, next_in_as);
|
||
|
list_delete_named(next_vr->mapped_resource->list_vr, next_vr,
|
||
|
prev_in_mapped_resource, next_in_mapped_resource);
|
||
|
|
||
|
if (next_vr->ops && next_vr->ops->unref)
|
||
|
next_vr->ops->unref(next_vr);
|
||
|
|
||
|
sos_kmem_vmm_free((sos_vaddr_t) next_vr);
|
||
|
}
|
||
|
else if (merge_with_preceding)
|
||
|
{
|
||
|
/* Widen the prev_vr VR to encompass the new VR */
|
||
|
vr = prev_vr;
|
||
|
vr->size += size;
|
||
|
}
|
||
|
else if (merge_with_next)
|
||
|
{
|
||
|
/* Widen the next_vr VR to encompass the new VR */
|
||
|
vr = next_vr;
|
||
|
vr->start -= size;
|
||
|
vr->size += size;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Allocate a brand new VR and insert it into the list */
|
||
|
|
||
|
vr = preallocated_vr;
|
||
|
used_preallocated_vr = TRUE;
|
||
|
|
||
|
vr->start = hint_uaddr;
|
||
|
vr->size = size;
|
||
|
vr->access_rights = access_rights;
|
||
|
vr->flags = flags;
|
||
|
vr->mapped_resource = resource;
|
||
|
vr->offset_in_resource = offset_in_resource;
|
||
|
|
||
|
/* Insert VR in address space */
|
||
|
vr->address_space = as;
|
||
|
if (prev_vr)
|
||
|
list_insert_after_named(as->list_vr, prev_vr, vr,
|
||
|
prev_in_as, next_in_as);
|
||
|
else
|
||
|
list_add_head_named(as->list_vr, vr, prev_in_as, next_in_as);
|
||
|
|
||
|
list_add_tail_named(vr->mapped_resource->list_vr, vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
|
||
|
/* Signal the resource we are mapping it */
|
||
|
if (resource && resource->mmap)
|
||
|
{
|
||
|
retval = resource->mmap(vr);
|
||
|
if (SOS_OK != retval)
|
||
|
{
|
||
|
retval = sos_umem_vmm_unmap(as, vr->start, vr->size);
|
||
|
goto return_mmap;
|
||
|
}
|
||
|
|
||
|
/* The page_in method is MANDATORY for mapped resources */
|
||
|
SOS_ASSERT_FATAL(vr->ops && vr->ops->page_in);
|
||
|
}
|
||
|
|
||
|
if (vr->ops && vr->ops->ref)
|
||
|
vr->ops->ref(vr);
|
||
|
}
|
||
|
|
||
|
/* Ok, fine, we got it right ! Return the address to the caller */
|
||
|
*uaddr = hint_uaddr;
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
size, 0, vr->access_rights);
|
||
|
retval = SOS_OK;
|
||
|
|
||
|
return_mmap:
|
||
|
if (! used_preallocated_vr)
|
||
|
sos_kmem_vmm_free((sos_vaddr_t)preallocated_vr);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_unmap(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t uaddr, sos_size_t size)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr *vr, *preallocated_vr;
|
||
|
sos_bool_t used_preallocated_vr;
|
||
|
sos_bool_t need_to_change_address_space = FALSE;
|
||
|
|
||
|
if (! SOS_IS_PAGE_ALIGNED(uaddr))
|
||
|
return -SOS_EINVAL;
|
||
|
if (size <= 0)
|
||
|
return -SOS_EINVAL;
|
||
|
size = SOS_PAGE_ALIGN_SUP(size);
|
||
|
|
||
|
/* Make sure the uaddr is valid */
|
||
|
if (! SOS_PAGING_IS_USER_AREA(uaddr, size) )
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* In some cases, the unmapping might imply a VR to be split into
|
||
|
2. Actually, allocating a new VR can be a blocking operation, but
|
||
|
actually we can block now, it won't do no harm. But we must be
|
||
|
careful not to block later, while altering the VR lists: that's
|
||
|
why we pre-allocate now. */
|
||
|
used_preallocated_vr = FALSE;
|
||
|
preallocated_vr
|
||
|
= (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0);
|
||
|
if (! preallocated_vr)
|
||
|
return -SOS_ENOMEM;
|
||
|
|
||
|
/* Find any VR intersecting with the given interval */
|
||
|
vr = find_first_intersecting_vr(as, uaddr, size);
|
||
|
|
||
|
/* Unmap (part of) the VR covered by [uaddr .. uaddr+size[ */
|
||
|
while (NULL != vr)
|
||
|
{
|
||
|
/* Went past the end of the *circular* list => back at the
|
||
|
beginning ? */
|
||
|
if (vr->start + vr->size <= uaddr)
|
||
|
/* Yes, stop now */
|
||
|
break;
|
||
|
|
||
|
/* Went beyond the region to unmap ? */
|
||
|
if (uaddr + size <= vr->start)
|
||
|
/* Yes, stop now */
|
||
|
break;
|
||
|
|
||
|
/* VR totally unmapped ? */
|
||
|
if ((vr->start >= uaddr)
|
||
|
&& (vr->start + vr->size <= uaddr + size))
|
||
|
{
|
||
|
struct sos_umem_vmm_vr *next_vr;
|
||
|
|
||
|
/* Yes: signal we remove it completely */
|
||
|
if (vr->ops && vr->ops->unmap)
|
||
|
vr->ops->unmap(vr, vr->start, vr->size);
|
||
|
|
||
|
/* Remove it from the AS list now */
|
||
|
next_vr = vr->next_in_as;
|
||
|
if (next_vr == vr) /* singleton ? */
|
||
|
next_vr = NULL;
|
||
|
list_delete_named(as->list_vr, vr, prev_in_as, next_in_as);
|
||
|
|
||
|
/* Remove from the list of VRs mapping the resource */
|
||
|
list_delete_named(vr->mapped_resource->list_vr, vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
|
||
|
if (vr->ops && vr->ops->unref)
|
||
|
vr->ops->unref(vr);
|
||
|
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
vr->size, vr->access_rights, 0);
|
||
|
sos_kmem_vmm_free((sos_vaddr_t)vr);
|
||
|
|
||
|
/* Prepare next iteration */
|
||
|
vr = next_vr;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* unmapped region lies completely INSIDE the the VR */
|
||
|
else if ( (vr->start < uaddr)
|
||
|
&& (vr->start + vr->size > uaddr + size) )
|
||
|
{
|
||
|
/* VR has to be split into 2 */
|
||
|
|
||
|
/* Use the preallocated VR and copy the VR into it */
|
||
|
used_preallocated_vr = TRUE;
|
||
|
memcpy(preallocated_vr, vr, sizeof(*vr));
|
||
|
|
||
|
/* Adjust the start/size of both VRs */
|
||
|
preallocated_vr->start = uaddr + size;
|
||
|
preallocated_vr->size = vr->start + vr->size - (uaddr + size);
|
||
|
preallocated_vr->offset_in_resource += uaddr + size - vr->start;
|
||
|
vr->size = uaddr - vr->start;
|
||
|
|
||
|
/* Insert the new VR into the list */
|
||
|
list_insert_after_named(as->list_vr, vr, preallocated_vr,
|
||
|
prev_in_as, next_in_as);
|
||
|
list_add_tail_named(vr->mapped_resource->list_vr, preallocated_vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
|
||
|
/* Signal the changes to the underlying resource */
|
||
|
if (vr->ops && vr->ops->unmap)
|
||
|
vr->ops->unmap(vr, uaddr, size);
|
||
|
if (preallocated_vr->ops && preallocated_vr->ops->ref)
|
||
|
preallocated_vr->ops->ref(preallocated_vr);
|
||
|
|
||
|
/* Account for change in VRs */
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
size, vr->access_rights, 0);
|
||
|
|
||
|
/* No need to go further */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Unmapped region only affects the START address of the VR */
|
||
|
else if (uaddr <= vr->start)
|
||
|
{
|
||
|
sos_size_t translation = uaddr + size - vr->start;
|
||
|
|
||
|
/* Shift the VR */
|
||
|
vr->size -= translation;
|
||
|
vr->offset_in_resource += translation;
|
||
|
vr->start += translation;
|
||
|
|
||
|
/* Signal unmapping */
|
||
|
if (vr->ops && vr->ops->unmap)
|
||
|
vr->ops->unmap(vr, uaddr + size,
|
||
|
translation);
|
||
|
|
||
|
/* Account for change in VRs */
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
translation,
|
||
|
vr->access_rights, 0);
|
||
|
|
||
|
/* No need to go further, we reached the last VR that
|
||
|
overlaps the unmapped region */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Unmapped region only affects the ENDING address of the VR */
|
||
|
else if (uaddr + size >= vr->start + vr->size)
|
||
|
{
|
||
|
sos_size_t unmapped_size = vr->start + vr->size - uaddr;
|
||
|
|
||
|
/* Resize VR */
|
||
|
vr->size = uaddr - vr->start;
|
||
|
|
||
|
/* Signal unmapping */
|
||
|
if (vr->ops && vr->ops->unmap)
|
||
|
vr->ops->unmap(vr, uaddr, unmapped_size);
|
||
|
|
||
|
/* Account for change in VRs */
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
unmapped_size,
|
||
|
vr->access_rights, 0);
|
||
|
|
||
|
vr = vr->next_in_as;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
sos_display_fatal_error("BUG uaddr=%x sz=%x vr_start=%x, vr_sz=%x",
|
||
|
uaddr, size, vr->start, vr->size);
|
||
|
}
|
||
|
|
||
|
/* When called from mresize, the address space is already squatted,
|
||
|
so we don't have to change it again */
|
||
|
need_to_change_address_space
|
||
|
= (as != sos_thread_get_current()->squatted_address_space);
|
||
|
|
||
|
if (need_to_change_address_space)
|
||
|
SOS_ASSERT_FATAL(SOS_OK
|
||
|
== sos_thread_prepare_user_space_access(as,
|
||
|
(sos_vaddr_t)
|
||
|
NULL));
|
||
|
|
||
|
|
||
|
/* Begin independent sub-block */
|
||
|
{
|
||
|
sos_ret_t sz_unmapped = sos_paging_unmap_interval(uaddr, size);
|
||
|
SOS_ASSERT_FATAL(sz_unmapped >= 0);
|
||
|
as->phys_total -= sz_unmapped;
|
||
|
}
|
||
|
/* End independent sub-block */
|
||
|
|
||
|
if (need_to_change_address_space)
|
||
|
SOS_ASSERT_FATAL(SOS_OK == sos_thread_end_user_space_access());
|
||
|
|
||
|
if (! used_preallocated_vr)
|
||
|
sos_kmem_vmm_free((sos_vaddr_t)preallocated_vr);
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_chprot(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t uaddr, sos_size_t size,
|
||
|
sos_ui32_t new_access_rights)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr *start_vr, *vr,
|
||
|
*preallocated_middle_vr, *preallocated_right_vr;
|
||
|
sos_bool_t used_preallocated_middle_vr, used_preallocated_right_vr;
|
||
|
|
||
|
if (! SOS_IS_PAGE_ALIGNED(uaddr))
|
||
|
return -SOS_EINVAL;
|
||
|
if (size <= 0)
|
||
|
return -SOS_EINVAL;
|
||
|
size = SOS_PAGE_ALIGN_SUP(size);
|
||
|
|
||
|
/* Make sure the uaddr is valid */
|
||
|
if (! SOS_PAGING_IS_USER_AREA(uaddr, size) )
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Pre-allocate 2 new VRs (same reason as for unmap). Because chprot
|
||
|
may imply at most 2 regions to be split */
|
||
|
used_preallocated_middle_vr = FALSE;
|
||
|
used_preallocated_right_vr = FALSE;
|
||
|
preallocated_middle_vr
|
||
|
= (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0);
|
||
|
if (! preallocated_middle_vr)
|
||
|
return -SOS_ENOMEM;
|
||
|
preallocated_right_vr
|
||
|
= (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0);
|
||
|
if (! preallocated_right_vr)
|
||
|
{
|
||
|
sos_kmem_vmm_free((sos_vaddr_t)preallocated_middle_vr);
|
||
|
return -SOS_ENOMEM;
|
||
|
}
|
||
|
|
||
|
/* Find any VR intersecting with the given interval */
|
||
|
start_vr = find_first_intersecting_vr(as, uaddr, size);
|
||
|
if (NULL == start_vr)
|
||
|
return SOS_OK;
|
||
|
|
||
|
/* First of all: make sure we are allowed to change the access
|
||
|
rights of all the VRs concerned by the chprot */
|
||
|
vr = start_vr;
|
||
|
while (TRUE)
|
||
|
{
|
||
|
/* Went past the end of the *circular* list => back at the
|
||
|
begining ? */
|
||
|
if (vr->start + vr->size <= uaddr)
|
||
|
/* Yes, stop now */
|
||
|
break;
|
||
|
|
||
|
/* Went beyond the region to chprot ? */
|
||
|
if (uaddr + size < vr->start)
|
||
|
/* Yes, stop now */
|
||
|
break;
|
||
|
|
||
|
if (vr->flags & SOS_VR_MAP_SHARED)
|
||
|
{
|
||
|
/* Make sure the mapped resource allows the required
|
||
|
protection flags */
|
||
|
if ( ( (new_access_rights & SOS_VM_MAP_PROT_READ)
|
||
|
&& !(vr->mapped_resource->allowed_access_rights
|
||
|
& SOS_VM_MAP_PROT_READ) )
|
||
|
|| ( (new_access_rights & SOS_VM_MAP_PROT_WRITE)
|
||
|
&& !(vr->mapped_resource->allowed_access_rights
|
||
|
& SOS_VM_MAP_PROT_WRITE) )
|
||
|
|| ( (new_access_rights & SOS_VM_MAP_PROT_EXEC)
|
||
|
&& !(vr->mapped_resource->allowed_access_rights
|
||
|
& SOS_VM_MAP_PROT_EXEC) ) )
|
||
|
return -SOS_EPERM;
|
||
|
}
|
||
|
|
||
|
vr = vr->next_in_as;
|
||
|
}
|
||
|
|
||
|
/* Change the access rights of the VRs covered by [uaddr
|
||
|
.. uaddr+size[ */
|
||
|
vr = start_vr;
|
||
|
while (TRUE)
|
||
|
{
|
||
|
|
||
|
/* Went past the end of the *circular* list => back at the
|
||
|
begining ? */
|
||
|
if (vr->start + vr->size <= uaddr)
|
||
|
/* Yes, stop now */
|
||
|
break;
|
||
|
|
||
|
/* Went beyond the region to chprot ? */
|
||
|
if (uaddr + size <= vr->start)
|
||
|
/* Yes, stop now */
|
||
|
break;
|
||
|
|
||
|
/* Access rights unchanged ? */
|
||
|
if (vr->access_rights == new_access_rights)
|
||
|
/* nop */
|
||
|
{
|
||
|
vr = vr->next_in_as;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* VR totally chprot ? */
|
||
|
if ((vr->start >= uaddr)
|
||
|
&& (vr->start + vr->size <= uaddr + size))
|
||
|
{
|
||
|
/* Account for change in VRs */
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
vr->size, vr->access_rights,
|
||
|
new_access_rights);
|
||
|
vr->access_rights = new_access_rights;
|
||
|
|
||
|
if (vr->flags & SOS_VR_MAP_SHARED)
|
||
|
/* For shared mappings: effectively change the access
|
||
|
rights of the physical pages */
|
||
|
sos_paging_set_prot_of_interval(vr->start, vr->size,
|
||
|
new_access_rights);
|
||
|
else
|
||
|
/* Private mapping */
|
||
|
{
|
||
|
/* For private mappings, we set the new access_rights
|
||
|
only if it becomes read-only. For private mappings
|
||
|
that become writable, we don't do anything: we keep
|
||
|
the access rights unchanged to preserve the COW
|
||
|
semantics */
|
||
|
if (! (new_access_rights & SOS_VM_MAP_PROT_WRITE))
|
||
|
sos_paging_set_prot_of_interval(vr->start, vr->size,
|
||
|
new_access_rights);
|
||
|
}
|
||
|
|
||
|
vr = vr->next_in_as;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* chprot region lies completely INSIDE the VR */
|
||
|
else if ( (vr->start < uaddr)
|
||
|
&& (vr->start + vr->size > uaddr + size) )
|
||
|
{
|
||
|
/* VR has to be split into 3 */
|
||
|
|
||
|
/* Use the preallocated VRs and copy the VR into them */
|
||
|
SOS_ASSERT_FATAL(! used_preallocated_middle_vr);
|
||
|
SOS_ASSERT_FATAL(! used_preallocated_right_vr);
|
||
|
used_preallocated_middle_vr = TRUE;
|
||
|
memcpy(preallocated_middle_vr, vr, sizeof(*vr));
|
||
|
used_preallocated_right_vr = TRUE;
|
||
|
memcpy(preallocated_right_vr, vr, sizeof(*vr));
|
||
|
|
||
|
/* Adjust the start/size of the VRs */
|
||
|
preallocated_middle_vr->start = uaddr;
|
||
|
preallocated_middle_vr->size = size;
|
||
|
preallocated_right_vr->start = uaddr + size;
|
||
|
preallocated_right_vr->size = vr->start + vr->size
|
||
|
- (uaddr + size);
|
||
|
preallocated_middle_vr->offset_in_resource
|
||
|
+= uaddr - vr->start;
|
||
|
preallocated_right_vr->offset_in_resource
|
||
|
+= uaddr + size - vr->start;
|
||
|
vr->size = uaddr - vr->start;
|
||
|
|
||
|
/* Account for change in VRs */
|
||
|
preallocated_middle_vr->access_rights = new_access_rights;
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
size, vr->access_rights,
|
||
|
new_access_rights);
|
||
|
|
||
|
/* Insert the new VRs into the lists */
|
||
|
list_insert_after_named(as->list_vr, vr, preallocated_middle_vr,
|
||
|
prev_in_as, next_in_as);
|
||
|
list_insert_after_named(as->list_vr, preallocated_middle_vr,
|
||
|
preallocated_right_vr,
|
||
|
prev_in_as, next_in_as);
|
||
|
|
||
|
list_add_tail_named(vr->mapped_resource->list_vr,
|
||
|
preallocated_middle_vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
list_add_tail_named(vr->mapped_resource->list_vr,
|
||
|
preallocated_right_vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
|
||
|
/* Effectively change the access rights of the physical pages */
|
||
|
if (!(preallocated_middle_vr->flags & SOS_VR_MAP_SHARED)
|
||
|
&& (new_access_rights & SOS_VM_MAP_PROT_WRITE))
|
||
|
/* For private mappings with write access, prepare for COW */
|
||
|
sos_paging_prepare_COW(preallocated_middle_vr->start,
|
||
|
preallocated_middle_vr->size);
|
||
|
else
|
||
|
sos_paging_set_prot_of_interval(preallocated_middle_vr->start,
|
||
|
preallocated_middle_vr->size,
|
||
|
new_access_rights);
|
||
|
|
||
|
if (preallocated_right_vr->ops && preallocated_right_vr->ops->ref)
|
||
|
preallocated_right_vr->ops->ref(preallocated_right_vr);
|
||
|
if (preallocated_middle_vr->ops && preallocated_middle_vr->ops->ref)
|
||
|
preallocated_middle_vr->ops->ref(preallocated_middle_vr);
|
||
|
|
||
|
/* No need to go further */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Chprot region only affects the START address of the VR */
|
||
|
else if (uaddr <= vr->start)
|
||
|
{
|
||
|
/* Split the region into 2 */
|
||
|
sos_uoffset_t offset_in_region = uaddr + size - vr->start;
|
||
|
|
||
|
/* Use the preallocated VRs and copy the VR into them */
|
||
|
SOS_ASSERT_FATAL(! used_preallocated_middle_vr);
|
||
|
used_preallocated_middle_vr = TRUE;
|
||
|
memcpy(preallocated_middle_vr, vr, sizeof(*vr));
|
||
|
|
||
|
/* Adjust the start/size of the VRs */
|
||
|
preallocated_middle_vr->start += offset_in_region;
|
||
|
preallocated_middle_vr->size -= offset_in_region;
|
||
|
vr->size = offset_in_region;
|
||
|
preallocated_middle_vr->offset_in_resource += offset_in_region;
|
||
|
|
||
|
/* Account for change in VRs */
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
vr->size,
|
||
|
vr->access_rights,
|
||
|
new_access_rights);
|
||
|
vr->access_rights = new_access_rights;
|
||
|
|
||
|
/* Insert the new VR into the lists */
|
||
|
list_insert_after_named(as->list_vr, vr,
|
||
|
preallocated_middle_vr,
|
||
|
prev_in_as, next_in_as);
|
||
|
list_add_tail_named(vr->mapped_resource->list_vr,
|
||
|
preallocated_middle_vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
|
||
|
/* Effectively change the access rights of the physical pages */
|
||
|
if (!(vr->flags & SOS_VR_MAP_SHARED)
|
||
|
&& (new_access_rights & SOS_VM_MAP_PROT_WRITE))
|
||
|
/* For private mappings with write access, prepare for COW */
|
||
|
sos_paging_prepare_COW(vr->start, vr->size);
|
||
|
else
|
||
|
sos_paging_set_prot_of_interval(vr->start, vr->size,
|
||
|
new_access_rights);
|
||
|
|
||
|
if (preallocated_middle_vr->ops && preallocated_middle_vr->ops->ref)
|
||
|
preallocated_middle_vr->ops->ref(preallocated_middle_vr);
|
||
|
|
||
|
/* Ne need to go further (we reached the last VR that
|
||
|
overlaps the given interval to chprot) */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Chprot region only affects the ENDING address of the VR */
|
||
|
else if (uaddr + size >= vr->start + vr->size)
|
||
|
{
|
||
|
/* Split the region into 2 */
|
||
|
sos_uoffset_t offset_in_region = uaddr - vr->start;
|
||
|
|
||
|
/* Use the preallocated VRs and copy the VR into them */
|
||
|
SOS_ASSERT_FATAL(! used_preallocated_right_vr);
|
||
|
used_preallocated_right_vr = TRUE;
|
||
|
memcpy(preallocated_right_vr, vr, sizeof(*vr));
|
||
|
|
||
|
/* Adjust the start/size of the VRs */
|
||
|
preallocated_right_vr->start += offset_in_region;
|
||
|
preallocated_right_vr->size -= offset_in_region;
|
||
|
vr->size = offset_in_region;
|
||
|
preallocated_right_vr->offset_in_resource += offset_in_region;
|
||
|
|
||
|
/* Account for change in VRs */
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
preallocated_right_vr->size,
|
||
|
vr->access_rights,
|
||
|
new_access_rights);
|
||
|
preallocated_right_vr->access_rights = new_access_rights;
|
||
|
|
||
|
/* Insert the new VR into the lists */
|
||
|
list_insert_after_named(as->list_vr, vr,
|
||
|
preallocated_right_vr,
|
||
|
prev_in_as, next_in_as);
|
||
|
list_add_tail_named(vr->mapped_resource->list_vr,
|
||
|
preallocated_right_vr,
|
||
|
prev_in_mapped_resource,
|
||
|
next_in_mapped_resource);
|
||
|
|
||
|
/* Effectively change the access rights of the physical pages */
|
||
|
if (!(preallocated_right_vr->flags & SOS_VR_MAP_SHARED)
|
||
|
&& (new_access_rights & SOS_VM_MAP_PROT_WRITE))
|
||
|
/* For private mappings with write access, prepare for COW */
|
||
|
sos_paging_prepare_COW(preallocated_right_vr->start,
|
||
|
preallocated_right_vr->size);
|
||
|
else
|
||
|
sos_paging_set_prot_of_interval(preallocated_right_vr->start,
|
||
|
preallocated_right_vr->size,
|
||
|
new_access_rights);
|
||
|
|
||
|
if (preallocated_right_vr->ops && preallocated_right_vr->ops->ref)
|
||
|
preallocated_right_vr->ops->ref(preallocated_right_vr);
|
||
|
|
||
|
vr = vr->next_in_as;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
sos_display_fatal_error("BUG");
|
||
|
}
|
||
|
|
||
|
if (! used_preallocated_middle_vr)
|
||
|
sos_kmem_vmm_free((sos_vaddr_t)preallocated_middle_vr);
|
||
|
if (! used_preallocated_right_vr)
|
||
|
sos_kmem_vmm_free((sos_vaddr_t)preallocated_right_vr);
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_sync(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t uaddr, sos_size_t size,
|
||
|
sos_ui32_t flags)
|
||
|
{
|
||
|
if (! SOS_IS_PAGE_ALIGNED(uaddr))
|
||
|
return -SOS_EINVAL;
|
||
|
if (size <= 0)
|
||
|
return -SOS_EINVAL;
|
||
|
size = SOS_PAGE_ALIGN_SUP(size);
|
||
|
|
||
|
/* Make sure the uaddr is valid */
|
||
|
if (! SOS_PAGING_IS_USER_AREA(uaddr, size) )
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Go from page to page, and for each dirty page in the region, call
|
||
|
the sync_page method */
|
||
|
while (TRUE)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr *vr;
|
||
|
|
||
|
if (size <= 0)
|
||
|
break;
|
||
|
|
||
|
/* Find any VR intersecting with the given interval */
|
||
|
vr = find_first_intersecting_vr(as, uaddr, size);
|
||
|
if (NULL == vr)
|
||
|
break;
|
||
|
|
||
|
/* For private or anonymous mappings => no backing store */
|
||
|
if ( !(vr->flags & SOS_VR_MAP_SHARED)
|
||
|
|| (vr->mapped_resource->flags & SOS_MAPPED_RESOURCE_ANONYMOUS)
|
||
|
|
||
|
/* Likewise for non msync-able regions */
|
||
|
|| ! vr->ops->sync_page )
|
||
|
{
|
||
|
if (size <= vr->size)
|
||
|
break;
|
||
|
|
||
|
uaddr += vr->size;
|
||
|
size -= vr->size;
|
||
|
}
|
||
|
|
||
|
/* Find the next dirty page in this VR */
|
||
|
for ( ; (size > 0)
|
||
|
&& (uaddr - vr->start < vr->size) ;
|
||
|
uaddr += SOS_PAGE_SIZE,
|
||
|
size -= SOS_PAGE_SIZE)
|
||
|
if (sos_paging_is_dirty(uaddr))
|
||
|
{
|
||
|
/* Synchronize it with its backing store */
|
||
|
vr->ops->sync_page(vr, uaddr, flags);
|
||
|
uaddr += SOS_PAGE_SIZE;
|
||
|
size -= SOS_PAGE_SIZE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_resize(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t old_uaddr, sos_size_t old_size,
|
||
|
sos_uaddr_t *new_uaddr, sos_size_t new_size,
|
||
|
sos_ui32_t flags)
|
||
|
{
|
||
|
sos_luoffset_t new_offset_in_resource;
|
||
|
sos_bool_t must_move_vr = FALSE;
|
||
|
struct sos_umem_vmm_vr *vr, *prev_vr, *next_vr;
|
||
|
|
||
|
/* Make sure the new uaddr is valid */
|
||
|
if (! SOS_PAGING_IS_USER_AREA(*new_uaddr, new_size) )
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
old_uaddr = SOS_PAGE_ALIGN_INF(old_uaddr);
|
||
|
old_size = SOS_PAGE_ALIGN_SUP(old_size);
|
||
|
if (! SOS_IS_PAGE_ALIGNED(*new_uaddr))
|
||
|
return -SOS_EINVAL;
|
||
|
if (new_size <= 0)
|
||
|
return -SOS_EINVAL;
|
||
|
new_size = SOS_PAGE_ALIGN_SUP(new_size);
|
||
|
|
||
|
/* Lookup a VR overlapping the address range */
|
||
|
vr = find_first_intersecting_vr(as, old_uaddr, old_size);
|
||
|
if (! vr)
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Make sure there is exactly ONE VR overlapping the area */
|
||
|
if ( (vr->start > old_uaddr)
|
||
|
|| (vr->start + vr->size < old_uaddr + old_size) )
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Retrieve the prev/next VR if they exist (the VR are on circular
|
||
|
list) */
|
||
|
prev_vr = vr->prev_in_as;
|
||
|
if (prev_vr->start >= vr->start)
|
||
|
prev_vr = NULL;
|
||
|
next_vr = vr->prev_in_as;
|
||
|
if (next_vr->start <= vr->start)
|
||
|
next_vr = NULL;
|
||
|
|
||
|
/*
|
||
|
* Compute new offset inside the mapped resource, if any
|
||
|
*/
|
||
|
|
||
|
/* Don't allow to resize if the uaddr goes beyond the 'offset 0' of
|
||
|
the resource */
|
||
|
if ( (*new_uaddr < vr->start)
|
||
|
&& (vr->start - *new_uaddr > vr->offset_in_resource) )
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* Compute new offset in the resource (overflow-safe) */
|
||
|
if (vr->start > *new_uaddr)
|
||
|
new_offset_in_resource
|
||
|
= vr->offset_in_resource
|
||
|
- (vr->start - *new_uaddr);
|
||
|
else
|
||
|
new_offset_in_resource
|
||
|
= vr->offset_in_resource
|
||
|
+ (*new_uaddr - vr->start);
|
||
|
|
||
|
/* If other VRs would be affected by this resizing, then the VR must
|
||
|
be moved */
|
||
|
if (prev_vr && (prev_vr->start + prev_vr->size > *new_uaddr))
|
||
|
must_move_vr |= TRUE;
|
||
|
if (next_vr && (next_vr->start < *new_uaddr + new_size))
|
||
|
must_move_vr |= TRUE;
|
||
|
|
||
|
/* If VR would be out-of-user-space, it must be moved */
|
||
|
if (! SOS_PAGING_IS_USER_AREA(*new_uaddr, new_size) )
|
||
|
must_move_vr |= TRUE;
|
||
|
|
||
|
/* The VR must be moved but the user forbids it */
|
||
|
if ( must_move_vr && !(flags & SOS_VR_REMAP_MAYMOVE) )
|
||
|
return -SOS_EINVAL;
|
||
|
|
||
|
/* If the VR must be moved, we simply map the resource elsewhere and
|
||
|
unmap the current VR */
|
||
|
if (must_move_vr)
|
||
|
{
|
||
|
sos_uaddr_t uaddr, result_uaddr;
|
||
|
sos_ret_t retval;
|
||
|
|
||
|
result_uaddr = *new_uaddr;
|
||
|
retval = sos_umem_vmm_map(as, & result_uaddr, new_size,
|
||
|
vr->access_rights,
|
||
|
vr->flags | INTERNAL_MAP_CALLED_FROM_MREMAP,
|
||
|
vr->mapped_resource,
|
||
|
new_offset_in_resource);
|
||
|
if (SOS_OK != retval)
|
||
|
return retval;
|
||
|
|
||
|
/* Remap the physical pages at their new address */
|
||
|
for (uaddr = vr->start ;
|
||
|
uaddr < vr->start + vr->size ;
|
||
|
uaddr += SOS_PAGE_SIZE)
|
||
|
{
|
||
|
sos_paddr_t paddr;
|
||
|
sos_ui32_t prot;
|
||
|
sos_uaddr_t vaddr;
|
||
|
|
||
|
if (uaddr < *new_uaddr)
|
||
|
continue;
|
||
|
if (uaddr > *new_uaddr + new_size)
|
||
|
continue;
|
||
|
|
||
|
/* Compute destination virtual address (should be
|
||
|
overflow-safe) */
|
||
|
if (vr->start >= *new_uaddr)
|
||
|
vaddr = result_uaddr
|
||
|
+ (uaddr - vr->start)
|
||
|
+ (vr->start - *new_uaddr);
|
||
|
else
|
||
|
vaddr = result_uaddr
|
||
|
+ (uaddr - vr->start)
|
||
|
- (*new_uaddr - vr->start);
|
||
|
|
||
|
paddr = sos_paging_get_paddr(uaddr);
|
||
|
if (! paddr)
|
||
|
/* No physical page mapped at this address yet */
|
||
|
continue;
|
||
|
|
||
|
prot = sos_paging_get_prot(uaddr);
|
||
|
SOS_ASSERT_FATAL(prot);
|
||
|
|
||
|
/* Remap it at its destination address */
|
||
|
retval = sos_paging_map(paddr, vaddr, TRUE, prot);
|
||
|
if (SOS_OK != retval)
|
||
|
{
|
||
|
sos_umem_vmm_unmap(as, result_uaddr, new_size);
|
||
|
return retval;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
retval = sos_umem_vmm_unmap(as, vr->start, vr->size);
|
||
|
if (SOS_OK != retval)
|
||
|
{
|
||
|
sos_umem_vmm_unmap(as, result_uaddr, new_size);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
*new_uaddr = result_uaddr;
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
/* Otherwise we simply resize the VR, taking care of unmapping
|
||
|
what's been unmapped */
|
||
|
|
||
|
if (*new_uaddr + new_size < vr->start + vr->size)
|
||
|
sos_umem_vmm_unmap(as, *new_uaddr + new_size,
|
||
|
vr->start + vr->size - (*new_uaddr + new_size));
|
||
|
else
|
||
|
{
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
*new_uaddr + new_size
|
||
|
- (vr->start + vr->size),
|
||
|
0, vr->access_rights);
|
||
|
vr->size += *new_uaddr + new_size - (vr->start + vr->size);
|
||
|
}
|
||
|
|
||
|
if (*new_uaddr > vr->start)
|
||
|
sos_umem_vmm_unmap(as, vr->start, *new_uaddr - vr->start);
|
||
|
else
|
||
|
{
|
||
|
as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED,
|
||
|
vr->start - *new_uaddr,
|
||
|
0, vr->access_rights);
|
||
|
vr->size += vr->start - *new_uaddr;
|
||
|
vr->start = *new_uaddr;
|
||
|
vr->offset_in_resource = new_offset_in_resource;
|
||
|
}
|
||
|
|
||
|
SOS_ASSERT_FATAL(vr->start == *new_uaddr);
|
||
|
SOS_ASSERT_FATAL(vr->size == new_size);
|
||
|
SOS_ASSERT_FATAL(vr->offset_in_resource == new_offset_in_resource);
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t sos_umem_vmm_try_resolve_page_fault(sos_uaddr_t uaddr,
|
||
|
sos_bool_t write_access,
|
||
|
sos_bool_t user_access)
|
||
|
{
|
||
|
struct sos_umem_vmm_as *as;
|
||
|
struct sos_umem_vmm_vr *vr;
|
||
|
|
||
|
as = current_address_space;
|
||
|
if (! as)
|
||
|
return -SOS_EFAULT;
|
||
|
|
||
|
vr = find_first_intersecting_vr(as, uaddr, 1);
|
||
|
if (! vr)
|
||
|
return -SOS_EFAULT;
|
||
|
|
||
|
/* Write on a read-only VR */
|
||
|
if (write_access && !(vr->access_rights & SOS_VM_MAP_PROT_WRITE))
|
||
|
return -SOS_EFAULT;
|
||
|
|
||
|
/* Write on a COW VR */
|
||
|
if (write_access && !(vr->flags & SOS_VR_MAP_SHARED))
|
||
|
{
|
||
|
if (SOS_OK == sos_paging_try_resolve_COW(uaddr))
|
||
|
{
|
||
|
as->pgflt_cow ++;
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Ask the underlying resource to resolve the page fault */
|
||
|
if (SOS_OK != vr->ops->page_in(vr, uaddr, write_access))
|
||
|
{
|
||
|
as->pgflt_invalid ++;
|
||
|
return -SOS_EFAULT;
|
||
|
}
|
||
|
|
||
|
as->phys_total += SOS_PAGE_SIZE;
|
||
|
as->pgflt_page_in ++;
|
||
|
|
||
|
/* For a private mapping, keep the mapping read-only */
|
||
|
if (!(vr->flags & SOS_VR_MAP_SHARED))
|
||
|
{
|
||
|
sos_paging_prepare_COW(SOS_PAGE_ALIGN_INF(uaddr),
|
||
|
SOS_PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_ret_t
|
||
|
sos_umem_vmm_init_heap(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t heap_start)
|
||
|
{
|
||
|
SOS_ASSERT_FATAL(! as->heap_start);
|
||
|
|
||
|
as->heap_start = heap_start;
|
||
|
as->heap_size = 0;
|
||
|
return SOS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
sos_uaddr_t
|
||
|
sos_umem_vmm_brk(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t new_top_uaddr)
|
||
|
{
|
||
|
sos_uaddr_t new_start;
|
||
|
sos_size_t new_size;
|
||
|
SOS_ASSERT_FATAL(as->heap_start);
|
||
|
|
||
|
if (! new_top_uaddr)
|
||
|
return as->heap_start + as->heap_size;
|
||
|
|
||
|
if (new_top_uaddr == as->heap_start + as->heap_size)
|
||
|
return as->heap_start + as->heap_size;
|
||
|
|
||
|
if (new_top_uaddr < as->heap_start)
|
||
|
return (sos_uaddr_t)NULL;
|
||
|
|
||
|
new_top_uaddr = SOS_PAGE_ALIGN_SUP(new_top_uaddr);
|
||
|
new_start = as->heap_start;
|
||
|
new_size = new_top_uaddr - as->heap_start;
|
||
|
|
||
|
/* First call to brk: we must map /dev/zero */
|
||
|
if (! as->heap_size)
|
||
|
{
|
||
|
if (SOS_OK != sos_dev_zero_map(as, & as->heap_start,
|
||
|
new_size,
|
||
|
SOS_VM_MAP_PROT_READ
|
||
|
| SOS_VM_MAP_PROT_WRITE,
|
||
|
0 /* private non-fixed */))
|
||
|
return (sos_uaddr_t)NULL;
|
||
|
|
||
|
as->heap_size = new_size;
|
||
|
return as->heap_start + as->heap_size;
|
||
|
}
|
||
|
|
||
|
/* Otherwise we just have to unmap or resize the region */
|
||
|
if (new_size <= 0)
|
||
|
{
|
||
|
if (SOS_OK != sos_umem_vmm_unmap(as,
|
||
|
as->heap_start, as->heap_size))
|
||
|
return (sos_uaddr_t)NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (SOS_OK != sos_umem_vmm_resize(as,
|
||
|
as->heap_start, as->heap_size,
|
||
|
& new_start, new_size,
|
||
|
0))
|
||
|
return (sos_uaddr_t)NULL;
|
||
|
}
|
||
|
|
||
|
SOS_ASSERT_FATAL(new_start == as->heap_start);
|
||
|
as->heap_size = new_size;
|
||
|
return new_top_uaddr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static struct sos_umem_vmm_vr *
|
||
|
find_enclosing_or_next_vr(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t uaddr)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr *vr;
|
||
|
int nb_vr;
|
||
|
|
||
|
if (! SOS_PAGING_IS_USER_AREA(uaddr, 1) )
|
||
|
return NULL;
|
||
|
|
||
|
list_foreach_named(as->list_vr, vr, nb_vr, prev_in_as, next_in_as)
|
||
|
{
|
||
|
/* Equivalent to "if (uaddr < vr->start + vr->size)" but more
|
||
|
robust (resilient to integer overflows) */
|
||
|
if (uaddr <= vr->start + (vr->size - 1))
|
||
|
return vr;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
static struct sos_umem_vmm_vr *
|
||
|
find_first_intersecting_vr(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t start_uaddr, sos_size_t size)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr * vr;
|
||
|
vr = find_enclosing_or_next_vr(as, start_uaddr);
|
||
|
if (! vr)
|
||
|
return NULL;
|
||
|
|
||
|
if (start_uaddr + size <= vr->start)
|
||
|
return NULL;
|
||
|
|
||
|
return vr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static sos_uaddr_t
|
||
|
find_first_free_interval(struct sos_umem_vmm_as * as,
|
||
|
sos_uaddr_t hint_uaddr, sos_size_t size)
|
||
|
{
|
||
|
struct sos_umem_vmm_vr * initial_vr, * vr;
|
||
|
|
||
|
if (hint_uaddr < SOS_PAGING_BASE_USER_ADDRESS)
|
||
|
hint_uaddr = SOS_PAGING_BASE_USER_ADDRESS;
|
||
|
|
||
|
if (hint_uaddr > SOS_PAGING_UPPER_USER_ADDRESS - size + 1)
|
||
|
return (sos_uaddr_t)NULL;
|
||
|
|
||
|
initial_vr = vr = find_enclosing_or_next_vr(as, hint_uaddr);
|
||
|
if (! vr)
|
||
|
/* Great, there is nothing after ! */
|
||
|
return hint_uaddr;
|
||
|
|
||
|
/* Scan the remaining VRs in the list */
|
||
|
do
|
||
|
{
|
||
|
/* Is there enough space /before/ that VR ? */
|
||
|
if (hint_uaddr + size <= vr->start)
|
||
|
/* Great ! */
|
||
|
return hint_uaddr;
|
||
|
|
||
|
/* Is there any VR /after/ this one, or do we have to wrap back
|
||
|
at the begining of the user space ? */
|
||
|
if (vr->next_in_as->start >= hint_uaddr)
|
||
|
/* Ok, the next VR is really after us */
|
||
|
hint_uaddr = vr->start + vr->size;
|
||
|
else
|
||
|
{
|
||
|
/* No: wrapping up */
|
||
|
|
||
|
/* Is there any space before the end of user space ? */
|
||
|
if (hint_uaddr <= SOS_PAGING_UPPER_USER_ADDRESS - size)
|
||
|
return hint_uaddr;
|
||
|
|
||
|
hint_uaddr = SOS_PAGING_BASE_USER_ADDRESS;
|
||
|
}
|
||
|
|
||
|
/* Prepare to look after this VR */
|
||
|
vr = vr->next_in_as;
|
||
|
}
|
||
|
while (vr != initial_vr);
|
||
|
|
||
|
/* Reached the end of the list and did not find anything ?... Look
|
||
|
at the space after the last VR */
|
||
|
|
||
|
return (sos_uaddr_t)NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
as_account_change_of_vr_protection(struct sos_umem_vmm_as * as,
|
||
|
sos_bool_t is_shared,
|
||
|
sos_size_t size,
|
||
|
sos_ui32_t prev_access_rights,
|
||
|
sos_ui32_t new_access_rights)
|
||
|
{
|
||
|
if (prev_access_rights == new_access_rights)
|
||
|
return;
|
||
|
|
||
|
#define _UPDATE_VMSTAT(field,is_increment) \
|
||
|
({ if (is_increment > 0) \
|
||
|
as->field += size; \
|
||
|
else \
|
||
|
{ SOS_ASSERT_FATAL(as->field >= size); as->field -= size; } })
|
||
|
#define UPDATE_VMSTAT(field,is_increment) \
|
||
|
({ if (is_shared) _UPDATE_VMSTAT(vm_shrd.field, is_increment); \
|
||
|
_UPDATE_VMSTAT(vm_total.field, is_increment); \
|
||
|
SOS_ASSERT_FATAL(as->vm_total.field >= as->vm_shrd.field); })
|
||
|
|
||
|
if ( (new_access_rights & SOS_VM_MAP_PROT_WRITE)
|
||
|
&& !(prev_access_rights & SOS_VM_MAP_PROT_WRITE))
|
||
|
{
|
||
|
UPDATE_VMSTAT(rw, +1);
|
||
|
if (prev_access_rights & SOS_VM_MAP_PROT_READ)
|
||
|
UPDATE_VMSTAT(ro, -1);
|
||
|
}
|
||
|
else if ( !(new_access_rights & SOS_VM_MAP_PROT_WRITE)
|
||
|
&& (prev_access_rights & SOS_VM_MAP_PROT_WRITE))
|
||
|
{
|
||
|
if (new_access_rights & SOS_VM_MAP_PROT_READ)
|
||
|
UPDATE_VMSTAT(ro, +1);
|
||
|
UPDATE_VMSTAT(rw, -1);
|
||
|
}
|
||
|
else if (new_access_rights & SOS_VM_MAP_PROT_READ)
|
||
|
UPDATE_VMSTAT(ro, +1);
|
||
|
else if (!(new_access_rights & SOS_VM_MAP_PROT_READ))
|
||
|
UPDATE_VMSTAT(ro, -1);
|
||
|
|
||
|
if ( (new_access_rights & SOS_VM_MAP_PROT_EXEC)
|
||
|
&& !(prev_access_rights & SOS_VM_MAP_PROT_EXEC))
|
||
|
{
|
||
|
UPDATE_VMSTAT(code, +1);
|
||
|
}
|
||
|
else if ( !(new_access_rights & SOS_VM_MAP_PROT_EXEC)
|
||
|
&& (prev_access_rights & SOS_VM_MAP_PROT_EXEC))
|
||
|
{
|
||
|
UPDATE_VMSTAT(code, -1);
|
||
|
}
|
||
|
|
||
|
if (new_access_rights && !prev_access_rights)
|
||
|
UPDATE_VMSTAT(overall, +1);
|
||
|
else if (!new_access_rights && prev_access_rights)
|
||
|
UPDATE_VMSTAT(overall, -1);
|
||
|
|
||
|
}
|