blob: 62e43bfd21f894073d1c49e300d0ee2c6808363d [file] [log] [blame]
/* Copyright (c) 2010 The Regents of the University of California
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* Page mapping: maps an object (inode or block dev) in page size chunks.
* Analagous to Linux's "struct address space" */
#include <pmap.h>
#include <atomic.h>
#include <radix.h>
#include <kref.h>
#include <assert.h>
#include <stdio.h>
void pm_add_vmr(struct page_map *pm, struct vm_region *vmr)
{
/* note that the VMR being reverse-mapped by the PM is protected by the PM's
* lock. so later when removal holds this, it delays munmaps and keeps the
* VMR connected. */
spin_lock(&pm->pm_lock);
TAILQ_INSERT_TAIL(&pm->pm_vmrs, vmr, vm_pm_link);
spin_unlock(&pm->pm_lock);
}
void pm_remove_vmr(struct page_map *pm, struct vm_region *vmr)
{
spin_lock(&pm->pm_lock);
TAILQ_REMOVE(&pm->pm_vmrs, vmr, vm_pm_link);
spin_unlock(&pm->pm_lock);
}
/* Initializes a PM. Host should be an *inode or a *bdev (doesn't matter). The
* reference this stores is uncounted. */
void pm_init(struct page_map *pm, struct page_map_operations *op, void *host)
{
pm->pm_bdev = host; /* note the uncounted ref */
radix_tree_init(&pm->pm_tree);
spinlock_init(&pm->pm_tree_lock);
pm->pm_num_pages = 0; /* no pages in a new pm */
pm->pm_op = op;
pm->pm_flags = 0;
spinlock_init(&pm->pm_lock);
TAILQ_INIT(&pm->pm_vmrs);
}
/* Looks up the index'th page in the page map, returning an incref'd reference,
* or 0 if it was not in the map. */
static struct page *pm_find_page(struct page_map *pm, unsigned long index)
{
spin_lock(&pm->pm_tree_lock);
struct page *page = (struct page*)radix_lookup(&pm->pm_tree, index);
if (page)
page_incref(page);
spin_unlock(&pm->pm_tree_lock);
return page;
}
/* Attempts to insert the page into the page_map, returns 0 for success, or an
* error code if there was one already (EEXIST) or we ran out of memory
* (ENOMEM). On success, this will preemptively lock the page, and will also
* store a reference to the page in the pm. */
static int pm_insert_page(struct page_map *pm, unsigned long index,
struct page *page)
{
int error = 0;
spin_lock(&pm->pm_tree_lock);
error = radix_insert(&pm->pm_tree, index, page);
if (!error) {
page_incref(page);
/* setting PG_BUF since we know it'll be used for IO later... */
atomic_or(&page->pg_flags, PG_LOCKED | PG_BUFFER | PG_PAGEMAP);
page->pg_sem.nr_signals = 0; /* ensure others will block */
page->pg_mapping = pm;
page->pg_index = index;
pm->pm_num_pages++;
}
spin_unlock(&pm->pm_tree_lock);
return error;
}
void pm_put_page(struct page *page)
{
page_decref(page);
}
/* Makes sure the index'th page of the mapped object is loaded in the page cache
* and returns its location via **pp. Note this will give you a refcnt'd
* reference to the page. This may block! TODO: (BLK) */
int pm_load_page(struct page_map *pm, unsigned long index, struct page **pp)
{
struct page *page;
int error;
bool page_was_mapped = TRUE;
page = pm_find_page(pm, index);
while (!page) {
/* kpage_alloc, since we want the page to persist after the proc
* dies (can be used by others, until the inode shuts down). */
if (kpage_alloc(&page))
return -ENOMEM;
/* might want to initialize other things, perhaps in page_alloc() */
atomic_set(&page->pg_flags, 0);
error = pm_insert_page(pm, index, page);
switch (error) {
case 0:
page_was_mapped = FALSE;
break;
case -EEXIST:
/* the page was mapped already (benign race), just get rid of
* our page and try again (the only case that uses the while) */
page_decref(page);
page = pm_find_page(pm, index);
break;
default:
/* something is wrong, bail out! */
page_decref(page);
return error;
}
}
assert(page && kref_refcnt(&page->pg_kref));
/* At this point, page is a refcnt'd page, and we return the reference.
* Also, there's an unlikely race where we're not in the page cache anymore,
* and this all is useless work. */
*pp = page;
/* if the page was in the map, we need to do some checks, and might have to
* read in the page later. If the page was freshly inserted to the pm by
* us, we skip this since we are the one doing the readpage(). */
if (page_was_mapped) {
/* is it already here and up to date? if so, we're done */
if (atomic_read(&page->pg_flags) & PG_UPTODATE)
return 0;
/* if not, try to lock the page (could BLOCK) */
lock_page(page);
/* we got it, is our page still in the cache? check the mapping. if
* not, start over, perhaps with EAGAIN and outside support */
if (!page->pg_mapping)
panic("Page is not in the mapping! Haven't implemented this!");
/* double check, are we up to date? if so, we're done */
if (atomic_read(&page->pg_flags) & PG_UPTODATE) {
unlock_page(page);
return 0;
}
}
/* if we're here, the page is locked by us, and it needs to be read in */
assert(page->pg_mapping == pm);
/* Readpage will block internally, returning when it is done */
error = pm->pm_op->readpage(pm, page);
assert(!error);
/* Unlock, since we're done with the page and it is up to date */
unlock_page(page);
assert(atomic_read(&page->pg_flags) & PG_UPTODATE);
return 0;
}
void print_page_map_info(struct page_map *pm)
{
struct vm_region *vmr_i;
printk("Page Map %p\n", pm);
printk("\tNum pages: %lu\n", pm->pm_num_pages);
spin_lock(&pm->pm_lock);
TAILQ_FOREACH(vmr_i, &pm->pm_vmrs, vm_pm_link) {
printk("\tVMR proc %d: (%p - %p): 0x%08x, 0x%08x, %p, %p\n",
vmr_i->vm_proc->pid, vmr_i->vm_base, vmr_i->vm_end,
vmr_i->vm_prot, vmr_i->vm_flags, vmr_i->vm_file, vmr_i->vm_foff);
}
spin_unlock(&pm->pm_lock);
}