--- /dev/null
+/* Copyright (c) 2007,2008 Sandia National Laboratories */
+
+#include <lwk/kernel.h>
+#include <lwk/task.h>
+#include <lwk/spinlock.h>
+#include <lwk/string.h>
+#include <lwk/aspace.h>
+#include <lwk/idspace.h>
+#include <lwk/htable.h>
+#include <lwk/log2.h>
+#include <lwk/cpuinfo.h>
+#include <lwk/pmem.h>
+#include <arch/uaccess.h>
+
+/**
+ * ID space used to allocate address space IDs.
+ */
+static idspace_t idspace;
+
+/**
+ * Hash table used to lookup address space structures by ID.
+ */
+static htable_t htable;
+
+/**
+ * Lock for serializing access to the htable.
+ */
+static DEFINE_SPINLOCK(htable_lock);
+
+/**
+ * Memory region structure. A memory region represents a contiguous region
+ * [start, end) of valid memory addresses in an address space.
+ */
+struct region {
+ struct aspace * aspace; /* Address space this region belongs to */
+ struct list_head link; /* Linkage in the aspace->region_list */
+
+ vaddr_t start; /* Starting address of the region */
+ vaddr_t end; /* 1st byte after end of the region */
+ vmflags_t flags; /* Permissions, caching, etc. */
+ vmpagesize_t pagesz; /* Allowed page sizes... 2^bit */
+ id_t smartmap; /* If (flags & VM_SMARTMAP), ID of the
+ aspace this region is mapped to */
+ char name[16]; /* Human-readable name of the region */
+};
+
+/**
+ * This calculates a region's end address. Normally end is the address of the
+ * first byte after the region. However if the region extends to the end of
+ * memory, that is not possible so set end to the last valid address,
+ * ULONG_MAX.
+ */
+static vaddr_t
+calc_end(vaddr_t start, size_t extent)
+{
+ vaddr_t end = start + extent;
+ if (end == 0)
+ end = ULONG_MAX;
+ return end;
+}
+
+/**
+ * Locates the region covering the specified address.
+ */
+static struct region *
+find_region(struct aspace *aspace, vaddr_t addr)
+{
+ struct region *rgn;
+
+ list_for_each_entry(rgn, &aspace->region_list, link) {
+ if ((rgn->start <= addr) && (rgn->end > addr))
+ return rgn;
+ }
+ return NULL;
+}
+
+/**
+ * Finds a region that overlaps the specified interval.
+ */
+static struct region *
+find_overlapping_region(struct aspace *aspace, vaddr_t start, vaddr_t end)
+{
+ struct region *rgn;
+
+ list_for_each_entry(rgn, &aspace->region_list, link) {
+ if ((start < rgn->end) && (end > rgn->start))
+ return rgn;
+ }
+ return NULL;
+}
+
+/**
+ * Locates the region that is SMARTMAP'ed to the specified aspace ID.
+ */
+static struct region *
+find_smartmap_region(struct aspace *aspace, id_t src_aspace)
+{
+ struct region *rgn;
+
+ list_for_each_entry(rgn, &aspace->region_list, link) {
+ if ((rgn->flags & VM_SMARTMAP) && (rgn->smartmap == src_aspace))
+ return rgn;
+ }
+ return NULL;
+}
+
+/**
+ * Looks up an aspace object by ID and returns it with its spinlock locked.
+ */
+static struct aspace *
+lookup_and_lock(id_t id)
+{
+ struct aspace *aspace;
+
+ /* Lock the hash table, lookup aspace object by ID */
+ spin_lock(&htable_lock);
+ if ((aspace = htable_lookup(htable, id)) == NULL) {
+ spin_unlock(&htable_lock);
+ return NULL;
+ }
+
+ /* Lock the identified aspace */
+ spin_lock(&aspace->lock);
+
+ /* Unlock the hash table, others may now use it */
+ spin_unlock(&htable_lock);
+
+ return aspace;
+}
+
+/**
+ * Like lookup_and_lock(), but looks up two address spaces instead of one.
+ */
+static int
+lookup_and_lock_two(id_t a, id_t b,
+ struct aspace **aspace_a, struct aspace **aspace_b)
+{
+ /* Lock the hash table, lookup aspace objects by ID */
+ spin_lock(&htable_lock);
+ if ((*aspace_a = htable_lookup(htable, a)) == NULL) {
+ spin_unlock(&htable_lock);
+ return -ENOENT;
+ }
+
+ if ((*aspace_b = htable_lookup(htable, b)) == NULL) {
+ spin_unlock(&htable_lock);
+ return -ENOENT;
+ }
+
+ /* Lock the identified aspaces */
+ spin_lock(&(*aspace_a)->lock);
+ spin_lock(&(*aspace_b)->lock);
+
+ /* Unlock the hash table, others may now use it */
+ spin_unlock(&htable_lock);
+
+ return 0;
+}
+
+static bool
+id_ok(id_t id)
+{
+ return ((id >= ASPACE_MIN_ID) && (id <= ASPACE_MAX_ID));
+}
+
+int __init
+aspace_subsys_init(void)
+{
+ int status;
+
+ /* Create an ID space for allocating address space IDs */
+ if ((status = idspace_create(__ASPACE_MIN_ID, __ASPACE_MAX_ID, &idspace)))
+ panic("Failed to create aspace ID space (status=%d).", status);
+
+ /* Create a hash table that will be used for quick ID->aspace lookups */
+ if ((status = htable_create(7 /* 2^7 bins */,
+ offsetof(struct aspace, id),
+ offsetof(struct aspace, ht_link),
+ &htable)))
+ panic("Failed to create aspace hash table (status=%d).", status);
+
+ /* Create an aspace for use by kernel threads */
+ if ((status = aspace_create(KERNEL_ASPACE_ID, "kernel", NULL)))
+ panic("Failed to create kernel aspace (status=%d).", status);
+
+ /* Switch to the newly created kernel address space */
+ if ((current->aspace = aspace_acquire(KERNEL_ASPACE_ID)) == NULL)
+ panic("Failed to acquire kernel aspace.");
+ arch_aspace_activate(current->aspace);
+
+ return 0;
+}
+
+int
+aspace_get_myid(id_t *id)
+{
+ *id = current->aspace->id;
+ return 0;
+}
+
+int
+sys_aspace_get_myid(id_t __user *id)
+{
+ int status;
+ id_t _id;
+
+ if ((status = aspace_get_myid(&_id)) != 0)
+ return status;
+
+ if (id && copy_to_user(id, &_id, sizeof(*id)))
+ return -EINVAL;
+
+ return 0;
+}
+
+int
+aspace_create(id_t id_request, const char *name, id_t *id)
+{
+ int status;
+ id_t new_id;
+ struct aspace *aspace;
+ unsigned long flags;
+
+ if ((status = idspace_alloc_id(idspace, id_request, &new_id)) != 0)
+ return status;
+
+ if ((aspace = kmem_alloc(sizeof(*aspace))) == NULL) {
+ idspace_free_id(idspace, new_id);
+ return -ENOMEM;
+ }
+
+ /*
+ * Initialize the address space. kmem_alloc() allocates zeroed memory
+ * so fields with an initial state of zero do not need to be explicitly
+ * initialized.
+ */
+ aspace->id = new_id;
+ spin_lock_init(&aspace->lock);
+ list_head_init(&aspace->region_list);
+ hlist_node_init(&aspace->ht_link);
+ if (name)
+ strlcpy(aspace->name, name, sizeof(aspace->name));
+
+ /* Create a region for the kernel portion of the address space */
+ status =
+ __aspace_add_region(
+ aspace,
+ PAGE_OFFSET,
+ ULONG_MAX-PAGE_OFFSET+1, /* # bytes to end of memory */
+ VM_KERNEL,
+ PAGE_SIZE,
+ "kernel"
+ );
+ if (status)
+ goto error1;
+
+ /* Do architecture-specific initialization */
+ if ((status = arch_aspace_create(aspace)) != 0)
+ goto error2;
+
+ /* Add new address space to a hash table, for quick lookups by ID */
+ spin_lock_irqsave(&htable_lock, flags);
+ BUG_ON(htable_add(htable, aspace));
+ spin_unlock_irqrestore(&htable_lock, flags);
+
+ if (id)
+ *id = new_id;
+ return 0;
+
+error2:
+ BUG_ON(__aspace_del_region(aspace,PAGE_OFFSET,ULONG_MAX-PAGE_OFFSET+1));
+error1:
+ idspace_free_id(idspace, aspace->id);
+ kmem_free(aspace);
+ return status;
+}
+
+int
+sys_aspace_create(id_t id_request, const char __user *name, id_t __user *id)
+{
+ int status;
+ char _name[16];
+ id_t _id;
+
+ if (current->uid != 0)
+ return -EPERM;
+
+ if ((id_request != ANY_ID) && !id_ok(id_request))
+ return -EINVAL;
+
+ if (strncpy_from_user(_name, name, sizeof(_name)) < 0)
+ return -EFAULT;
+ _name[sizeof(_name) - 1] = '\0';
+
+ if ((status = aspace_create(id_request, _name, &_id)) != 0)
+ return status;
+
+ BUG_ON(!id_ok(_id));
+
+ if (id && copy_to_user(id, &_id, sizeof(*id)))
+ return -EFAULT;
+
+ return 0;
+}
+
+int
+aspace_destroy(id_t id)
+{
+ struct aspace *aspace;
+ struct list_head *pos, *tmp;
+ struct region *rgn;
+ unsigned long irqstate;
+
+ /* Lock the hash table, lookup aspace object by ID */
+ spin_lock_irqsave(&htable_lock, irqstate);
+ if ((aspace = htable_lookup(htable, id)) == NULL) {
+ spin_unlock_irqrestore(&htable_lock, irqstate);
+ return -EINVAL;
+ }
+
+ /* Lock the identified aspace */
+ spin_lock(&aspace->lock);
+
+ if (aspace->refcnt) {
+ spin_unlock(&aspace->lock);
+ spin_unlock_irqrestore(&htable_lock, irqstate);
+ return -EBUSY;
+ }
+
+ /* Remove aspace from hash table, preventing others from finding it */
+ BUG_ON(htable_del(htable, aspace));
+
+ /* Unlock the hash table, others may now use it */
+ spin_unlock_irqrestore(&htable_lock, irqstate);
+ spin_unlock(&aspace->lock);
+
+ /* Finish up destroying the aspace, we have the only reference */
+ list_for_each_safe(pos, tmp, &aspace->region_list) {
+ rgn = list_entry(pos, struct region, link);
+ /* Must drop our reference on all SMARTMAP'ed aspaces */
+ if (rgn->flags & VM_SMARTMAP) {
+ struct aspace *src;
+ spin_lock_irqsave(&htable_lock, irqstate);
+ src = htable_lookup(htable, rgn->smartmap);
+ BUG_ON(src == NULL);
+ spin_lock(&src->lock);
+ --src->refcnt;
+ spin_unlock(&src->lock);
+ spin_unlock_irqrestore(&htable_lock, irqstate);
+ }
+ list_del(&rgn->link);
+ kmem_free(rgn);
+ }
+ arch_aspace_destroy(aspace);
+ BUG_ON(idspace_free_id(idspace, aspace->id));
+ kmem_free(aspace);
+ return 0;
+}
+
+int
+sys_aspace_destroy(id_t id)
+{
+ if (current->uid != 0)
+ return -EPERM;
+ if (!id_ok(id))
+ return -EINVAL;
+ return aspace_destroy(id);
+}
+
+/**
+ * Acquires an address space object. The object is guaranteed not to be
+ * deleted until it is released via aspace_release().
+ */
+struct aspace *
+aspace_acquire(id_t id)
+{
+ struct aspace *aspace;
+ unsigned long irqstate;
+
+ local_irq_save(irqstate);
+ if ((aspace = lookup_and_lock(id)) != NULL) {
+ ++aspace->refcnt;
+ spin_unlock(&aspace->lock);
+ }
+ local_irq_restore(irqstate);
+ return aspace;
+}
+
+/**
+ * Releases an aspace object that was previously acquired via aspace_acquire().
+ * The aspace object passed in must be unlocked.
+ */
+void
+aspace_release(struct aspace *aspace)
+{
+ unsigned long irqstate;
+ spin_lock_irqsave(&aspace->lock, irqstate);
+ --aspace->refcnt;
+ spin_unlock_irqrestore(&aspace->lock, irqstate);
+}
+
+int
+__aspace_find_hole(struct aspace *aspace,
+ vaddr_t start_hint, size_t extent, size_t alignment,
+ vaddr_t *start)
+{
+ struct region *rgn;
+ vaddr_t hole;
+
+ if (!aspace || !extent || !is_power_of_2(alignment))
+ return -EINVAL;
+
+ if (start_hint == 0)
+ start_hint = 1;
+
+ hole = round_up(start_hint, alignment);
+ while ((rgn = find_overlapping_region(aspace, hole, hole + extent))) {
+ if (rgn->end == ULONG_MAX)
+ return -ENOENT;
+ hole = round_up(rgn->end, alignment);
+ }
+
+ if (start)
+ *start = hole;
+ return 0;
+}
+
+int
+aspace_find_hole(id_t id,
+ vaddr_t start_hint, size_t extent, size_t alignment,
+ vaddr_t *start)
+{
+ int status;
+ struct aspace *aspace;
+ unsigned long irqstate;
+
+ local_irq_save(irqstate);
+ aspace = lookup_and_lock(id);
+ status = __aspace_find_hole(aspace, start_hint, extent, alignment,
+ start);
+ if (aspace) spin_unlock(&aspace->lock);
+ local_irq_restore(irqstate);
+ return status;
+}
+
+int
+sys_aspace_find_hole(id_t id,
+ vaddr_t start_hint, size_t extent, size_t alignment,
+ vaddr_t __user *start)
+{
+ vaddr_t _start;
+ int status;
+
+ if (current->uid != 0)
+ return -EPERM;
+
+ if (!id_ok(id))
+ return -EINVAL;
+
+ status = aspace_find_hole(id, start_hint, extent, alignment, &_start);
+ if (status)
+ return status;
+
+ if (start && copy_to_user(start, &_start, sizeof(_start)))
+ return -EFAULT;
+
+ return 0;
+}
+
+int
+__aspace_add_region(struct aspace *aspace,
+ vaddr_t start, size_t extent,
+ vmflags_t flags, vmpagesize_t pagesz,
+ const char *name)
+{
+ struct region *rgn;
+ struct region *cur;
+ struct list_head *pos;
+ vaddr_t end = calc_end(start, extent);
+
+ if (!aspace || !start)
+ return -EINVAL;
+
+ /* Region must have non-zero size */
+ if (extent == 0) {
+ printk(KERN_WARNING "Extent must be non-zero.\n");
+ return -EINVAL;
+ }
+
+ /* Region must have a positive size */
+ if (start >= end) {
+ printk(KERN_WARNING
+ "Invalid region size (start=0x%lx, extent=0x%lx).\n",
+ start, extent);
+ return -EINVAL;
+ }
+
+ /* Architecture must support the page size specified */
+ if ((pagesz & cpu_info[0].pagesz_mask) == 0) {
+ printk(KERN_WARNING
+ "Invalid page size specified (pagesz=0x%lx).\n",
+ pagesz);
+ return -EINVAL;
+ }
+ pagesz &= cpu_info[0].pagesz_mask;
+
+ /* Only one page size may be specified */
+ if (!is_power_of_2(pagesz)) {
+ printk(KERN_WARNING
+ "More than one page size specified (pagesz=0x%lx).\n",
+ pagesz);
+ return -EINVAL;
+ }
+
+ /* Region must be aligned to at least the specified page size */
+ if ((start & (pagesz-1)) || ((end!=ULONG_MAX) && (end & (pagesz-1)))) {
+ printk(KERN_WARNING
+ "Region is misaligned (start=0x%lx, end=0x%lx).\n",
+ start, end);
+ return -EINVAL;
+ }
+
+ /* Region must not overlap with any existing regions */
+ list_for_each_entry(cur, &aspace->region_list, link) {
+ if ((start < cur->end) && (end > cur->start)) {
+ printk(KERN_WARNING
+ "Region overlaps with existing region.\n");
+ return -ENOTUNIQ;
+ }
+ }
+
+ /* Allocate and initialize a new region object */
+ if ((rgn = kmem_alloc(sizeof(struct region))) == NULL)
+ return -ENOMEM;
+
+ rgn->aspace = aspace;
+ rgn->start = start;
+ rgn->end = end;
+ rgn->flags = flags;
+ rgn->pagesz = pagesz;
+ if (name)
+ strlcpy(rgn->name, name, sizeof(rgn->name));
+
+ /* The heap region is special, remember its bounds */
+ if (flags & VM_HEAP) {
+ aspace->heap_start = start;
+ aspace->heap_end = end;
+ aspace->brk = aspace->heap_start;
+ aspace->mmap_brk = aspace->heap_end;
+ }
+
+ /* Insert region into address space's sorted region list */
+ list_for_each(pos, &aspace->region_list) {
+ cur = list_entry(pos, struct region, link);
+ if (cur->start > rgn->start)
+ break;
+ }
+ list_add_tail(&rgn->link, pos);
+ return 0;
+}
+
+int
+aspace_add_region(id_t id,
+ vaddr_t start, size_t extent,
+ vmflags_t flags, vmpagesize_t pagesz,
+ const char *name)
+{
+ int status;
+ struct aspace *aspace;
+ unsigned long irqstate;
+
+ local_irq_save(irqstate);
+ aspace = lookup_and_lock(id);
+ status = __aspace_add_region(aspace, start, extent, flags, pagesz, name);
+ if (aspace) spin_unlock(&aspace->lock);
+ local_irq_restore(irqstate);
+ return status;
+}
+
+int
+sys_aspace_add_region(id_t id,
+ vaddr_t start, size_t extent,
+ vmflags_t flags, vmpagesize_t pagesz,
+ const char __user *name)
+{
+ char _name[16];
+
+ if (current->uid != 0)
+ return -EPERM;
+
+ if (!id_ok(id))
+ return -EINVAL;
+
+ if (strncpy_from_user(_name, name, sizeof(_name)) < 0)
+ return -EFAULT;
+ _name[sizeof(_name) - 1] = '\0';
+
+ return aspace_add_region(id, start, extent, flags, pagesz, _name);
+}
+
+
+int
+__aspace_del_region(struct aspace *aspace, vaddr_t start, size_t extent)
+{
+ int status;
+ struct region *rgn;
+ vaddr_t end = calc_end(start, extent);
+
+ if (!aspace)
+ return -EINVAL;
+
+ /* Locate the region to delete */
+ rgn = find_region(aspace, start);
+ if (!rgn || (rgn->start != start) || (rgn->end != end)
+ || (rgn->flags & VM_KERNEL))
+ return -EINVAL;
+
+ if (!(rgn->flags & VM_SMARTMAP)) {
+ /* Unmap all of the memory that was mapped to the region */
+ status = __aspace_unmap_pmem(aspace, start, extent);
+ if (status)
+ return status;
+ }
+
+ /* Remove the region from the address space */
+ list_del(&rgn->link);
+ kmem_free(rgn);
+ return 0;
+}
+
+int
+aspace_del_region(id_t id, vaddr_t start, size_t extent)
+{
+ int status;
+ struct aspace *aspace;
+ unsigned long irqstate;
+
+ local_irq_save(irqstate);
+ aspace = lookup_and_lock(id);
+ status = __aspace_del_region(aspace, start, extent);
+ if (aspace) spin_unlock(&aspace->lock);
+ local_irq_restore(irqstate);
+ return status;
+}
+
+int
+sys_aspace_del_region(id_t id, vaddr_t start, size_t extent)
+{
+ if (current->uid != 0)
+ return -EPERM;
+ if (!id_ok(id))
+ return -EINVAL;
+ return aspace_del_region(id, start, extent);
+}
+
+static int
+map_pmem(struct aspace *aspace,
+ paddr_t pmem, vaddr_t start, size_t extent,
+ bool umem_only)
+{
+ int status;
+ struct region *rgn;
+
+ if (!aspace)
+ return -EINVAL;
+
+ if (umem_only && !pmem_is_umem(pmem, extent)) {
+ printk(KERN_WARNING
+ "User-space tried to map non-UMEM "
+ "(pmem=0x%lx, extent=0x%lx).\n",
+ pmem, extent);
+ return -EPERM;
+ }
+
+ while (extent) {
+ /* Find region covering the address */
+ rgn = find_region(aspace, start);
+ if (!rgn) {
+ printk(KERN_WARNING
+ "Failed to find region covering addr=0x%lx.\n",
+ start);
+ return -EINVAL;
+ }
+
+ /* Can't map anything to kernel or SMARTMAP regions */
+ if ((rgn->flags & VM_KERNEL) || (rgn->flags & VM_SMARTMAP)) {
+ printk(KERN_WARNING
+ "Trying to map memory to protected region.\n");
+ return -EINVAL;
+ }
+
+ /* addresses must be aligned to region's page size */
+ if ((start & (rgn->pagesz-1)) || (pmem & (rgn->pagesz-1))) {
+ printk(KERN_WARNING
+ "Misalignment "
+ "(start=0x%lx, pmem=0x%lx, pagesz=0x%lx).\n",
+ start, pmem, rgn->pagesz);
+ return -EINVAL;
+ }
+
+ /* Map until full extent mapped or end of region is reached */
+ while (extent && (start < rgn->end)) {
+
+ status =
+ arch_aspace_map_page(
+ aspace,
+ start,
+ pmem,
+ rgn->flags,
+ rgn->pagesz
+ );
+ if (status)
+ return status;
+
+ extent -= rgn->pagesz;
+ start += rgn->pagesz;
+ pmem += rgn->pagesz;
+ }
+ }
+
+ return 0;
+}
+
+static int
+map_pmem_locked(id_t id,
+ paddr_t pmem, vaddr_t start, size_t extent,
+ bool umem_only)
+{
+ int status;
+ struct aspace *aspace;
+ unsigned long irqstate;
+
+ local_irq_save(irqstate);
+ aspace = lookup_and_lock(id);
+ status = map_pmem(aspace, pmem, start, extent, umem_only);
+ if (aspace) spin_unlock(&aspace->lock);
+ local_irq_restore(irqstate);
+ return status;
+}
+
+int
+__aspace_map_pmem(struct aspace *aspace,
+ paddr_t pmem, vaddr_t start, size_t extent)
+{
+ return map_pmem(aspace, pmem, start, extent, false);
+}
+
+int
+aspace_map_pmem(id_t id, paddr_t pmem, vaddr_t start, size_t extent)
+{
+ return map_pmem_locked(id, pmem, start, extent, false);
+}
+
+int
+sys_aspace_map_pmem(id_t id, paddr_t pmem, vaddr_t start, size_t extent)
+{
+ if (current->uid != 0)
+ return -EPERM;
+ if (!id_ok(id))
+ return -EINVAL;
+ return map_pmem_locked(id, pmem, start, extent, true);
+}
+
+int
+__aspace_unmap_pmem(struct aspace *aspace, vaddr_t start, size_t extent)
+{
+ struct region *rgn;
+
+ if (!aspace)
+ return -EINVAL;
+
+ while (extent) {
+ /* Find region covering the address */
+ rgn = find_region(aspace, start);
+ if (!rgn) {
+ printk(KERN_WARNING
+ "Failed to find region covering addr=0x%lx.\n",
+ start);
+ return -EINVAL;
+ }
+
+ /* Can't unmap anything from kernel or SMARTMAP regions */
+ if ((rgn->flags & VM_KERNEL) || (rgn->flags & VM_SMARTMAP)) {
+ printk(KERN_WARNING
+ "Trying to map memory to protected region.\n");
+ return -EINVAL;
+ }
+
+ /* address must be aligned to region's page size */
+ if (start & (rgn->pagesz-1)) {
+ printk(KERN_WARNING
+ "Misalignment (start=0x%lx, pagesz=0x%lx).\n",
+ start, rgn->pagesz);
+ return -EINVAL;
+ }
+
+ /* Unmap until full extent unmapped or end of region is reached */
+ while (extent && (start < rgn->end)) {
+
+ arch_aspace_unmap_page(
+ aspace,
+ start,
+ rgn->pagesz
+ );
+
+ extent -= rgn->pagesz;
+ start += rgn->pagesz;
+ }
+ }
+
+ return 0;
+}
+
+int
+aspace_unmap_pmem(id_t id, vaddr_t start, size_t extent)
+{
+ int status;
+ struct aspace *aspace;
+ unsigned long irqstate;
+
+ local_irq_save(irqstate);
+ aspace = lookup_and_lock(id);
+ status = __aspace_unmap_pmem(aspace, start, extent);
+ if (aspace) spin_unlock(&aspace->lock);
+ local_irq_restore(irqstate);
+ return status;
+}
+
+int
+sys_aspace_unmap_pmem(id_t id, vaddr_t start, size_t extent)
+{
+ if (current->uid != 0)
+ return -EPERM;
+ if (!id_ok(id))
+ return -EINVAL;
+ return aspace_unmap_pmem(id, start, extent);
+}
+
+int
+__aspace_smartmap(struct aspace *src, struct aspace *dst,
+ vaddr_t start, size_t extent)
+{
+ int status;
+ vaddr_t end = start + extent;
+ char name[16];
+ struct region *rgn;
+
+ /* Can only SMARTMAP a given aspace in once */
+ if (find_smartmap_region(dst, src->id))
+ return -EINVAL;
+
+ if (start >= end)
+ return -EINVAL;
+
+ if ((start & (SMARTMAP_ALIGN-1)) || (end & (SMARTMAP_ALIGN-1)))
+ return -EINVAL;
+
+ snprintf(name, sizeof(name), "SMARTMAP-%u", (unsigned int)src->id);
+ if ((status = __aspace_add_region(dst, start, extent,
+ VM_SMARTMAP, PAGE_SIZE, name)))
+ return status;
+
+ /* Do architecture-specific SMARTMAP initialization */
+ if ((status = arch_aspace_smartmap(src, dst, start, extent))) {
+ BUG_ON(__aspace_del_region(dst, start, extent));
+ return status;
+ }
+
+ /* Remember the source aspace that the SMARTMAP region is mapped to */
+ rgn = find_region(dst, start);
+ BUG_ON(!rgn);
+ rgn->smartmap = src->id;
+
+ /* Ensure source aspace doesn't go away while we have it SMARTMAP'ed */
+ ++src->refcnt;
+
+ return 0;
+}
+
+int
+aspace_smartmap(id_t src, id_t dst, vaddr_t start, size_t extent)
+{
+ int status;
+ struct aspace *src_spc, *dst_spc;
+ unsigned long irqstate;
+
+ /* Don't allow self SMARTMAP'ing */
+ if (src == dst)
+ return -EINVAL;
+
+ local_irq_save(irqstate);
+ if ((status = lookup_and_lock_two(src, dst, &src_spc, &dst_spc))) {
+ local_irq_restore(irqstate);
+ return status;
+ }
+ status = __aspace_smartmap(src_spc, dst_spc, start, extent);
+ spin_unlock(&src_spc->lock);
+ spin_unlock(&dst_spc->lock);
+ local_irq_restore(irqstate);
+ return status;
+}
+
+int
+sys_aspace_smartmap(id_t src, id_t dst, vaddr_t start, size_t extent)
+{
+ if (current->uid != 0)
+ return -EPERM;
+ if (!id_ok(src) || !id_ok(dst))
+ return -EINVAL;
+ return aspace_smartmap(src, dst, start, extent);
+}
+
+int
+__aspace_unsmartmap(struct aspace *src, struct aspace *dst)
+{
+ struct region *rgn;
+ size_t extent;
+
+ if ((rgn = find_smartmap_region(dst, src->id)) == NULL)
+ return -EINVAL;
+ extent = rgn->end - rgn->start;
+
+ /* Do architecture-specific SMARTMAP unmapping */
+ BUG_ON(arch_aspace_unsmartmap(src, dst, rgn->start, extent));
+
+ /* Delete the SMARTMAP region and release our reference on the source */
+ BUG_ON(__aspace_del_region(dst, rgn->start, extent));
+ --src->refcnt;
+
+ return 0;
+}
+
+int
+aspace_unsmartmap(id_t src, id_t dst)
+{
+ int status;
+ struct aspace *src_spc, *dst_spc;
+ unsigned long irqstate;
+
+ /* Don't allow self SMARTMAP'ing */
+ if (src == dst)
+ return -EINVAL;
+
+ local_irq_save(irqstate);
+ if ((status = lookup_and_lock_two(src, dst, &src_spc, &dst_spc))) {
+ local_irq_restore(irqstate);
+ return status;
+ }
+ status = __aspace_unsmartmap(src_spc, dst_spc);
+ spin_unlock(&src_spc->lock);
+ spin_unlock(&dst_spc->lock);
+ local_irq_restore(irqstate);
+ return status;
+}
+
+int
+sys_aspace_unsmartmap(id_t src, id_t dst)
+{
+ if (current->uid != 0)
+ return -EPERM;
+ if (!id_ok(src) || !id_ok(dst))
+ return -EINVAL;
+ return aspace_unsmartmap(src, dst);
+}
+
+int
+aspace_dump2console(id_t id)
+{
+ struct aspace *aspace;
+ struct region *rgn;
+ unsigned long irqstate;
+
+ local_irq_save(irqstate);
+
+ if ((aspace = lookup_and_lock(id)) == NULL) {
+ local_irq_restore(irqstate);
+ return -EINVAL;
+ }
+
+ printk(KERN_DEBUG "DUMP OF ADDRESS SPACE %u:\n", aspace->id);
+ printk(KERN_DEBUG " name: %s\n", aspace->name);
+ printk(KERN_DEBUG " refcnt: %d\n", aspace->refcnt);
+ printk(KERN_DEBUG " regions:\n");
+ list_for_each_entry(rgn, &aspace->region_list, link) {
+ printk(KERN_DEBUG
+ " [0x%016lx, 0x%016lx%c %s\n",
+ rgn->start,
+ rgn->end,
+ (rgn->end == ULONG_MAX) ? ']' : ')',
+ rgn->name
+ );
+ }
+
+ spin_unlock(&aspace->lock);
+ local_irq_restore(irqstate);
+ return 0;
+}
+
+int
+sys_aspace_dump2console(id_t id)
+{
+ return aspace_dump2console(id);
+}