VMRs that map page_maps are tracked
[akaros.git] / kern / src / pagemap.c
1 /* Copyright (c) 2010 The Regents of the University of California
2  * Barret Rhoden <brho@cs.berkeley.edu>
3  * See LICENSE for details.
4  *
5  * Page mapping: maps an object (inode or block dev) in page size chunks.
6  * Analagous to Linux's "struct address space" */
7
8 #include <pmap.h>
9 #include <atomic.h>
10 #include <radix.h>
11 #include <kref.h>
12 #include <assert.h>
13 #include <stdio.h>
14
15 void pm_add_vmr(struct page_map *pm, struct vm_region *vmr)
16 {
17         /* note that the VMR being reverse-mapped by the PM is protected by the PM's
18          * lock.  so later when removal holds this, it delays munmaps and keeps the
19          * VMR connected. */
20         spin_lock(&pm->pm_lock);
21         TAILQ_INSERT_TAIL(&pm->pm_vmrs, vmr, vm_pm_link);
22         spin_unlock(&pm->pm_lock);
23 }
24
25 void pm_remove_vmr(struct page_map *pm, struct vm_region *vmr)
26 {
27         spin_lock(&pm->pm_lock);
28         TAILQ_REMOVE(&pm->pm_vmrs, vmr, vm_pm_link);
29         spin_unlock(&pm->pm_lock);
30 }
31
32 /* Initializes a PM.  Host should be an *inode or a *bdev (doesn't matter).  The
33  * reference this stores is uncounted. */
34 void pm_init(struct page_map *pm, struct page_map_operations *op, void *host)
35 {
36         pm->pm_bdev = host;                                             /* note the uncounted ref */
37         radix_tree_init(&pm->pm_tree);
38         spinlock_init(&pm->pm_tree_lock);
39         pm->pm_num_pages = 0;                                   /* no pages in a new pm */
40         pm->pm_op = op;
41         pm->pm_flags = 0;
42         spinlock_init(&pm->pm_lock);
43         TAILQ_INIT(&pm->pm_vmrs);
44 }
45
46 /* Looks up the index'th page in the page map, returning an incref'd reference,
47  * or 0 if it was not in the map. */
48 static struct page *pm_find_page(struct page_map *pm, unsigned long index)
49 {
50         spin_lock(&pm->pm_tree_lock);
51         struct page *page = (struct page*)radix_lookup(&pm->pm_tree, index);
52         if (page)
53                 page_incref(page);
54         spin_unlock(&pm->pm_tree_lock);
55         return page;
56 }
57
58 /* Attempts to insert the page into the page_map, returns 0 for success, or an
59  * error code if there was one already (EEXIST) or we ran out of memory
60  * (ENOMEM).  On success, this will preemptively lock the page, and will also
61  * store a reference to the page in the pm. */
62 static int pm_insert_page(struct page_map *pm, unsigned long index,
63                           struct page *page)
64 {
65         int error = 0;
66         spin_lock(&pm->pm_tree_lock);
67         error = radix_insert(&pm->pm_tree, index, page);
68         if (!error) {
69                 page_incref(page);
70                 /* setting PG_BUF since we know it'll be used for IO later... */
71                 atomic_or(&page->pg_flags, PG_LOCKED | PG_BUFFER | PG_PAGEMAP);
72                 page->pg_sem.nr_signals = 0;            /* ensure others will block */
73                 page->pg_mapping = pm;
74                 page->pg_index = index;
75                 pm->pm_num_pages++;
76         }
77         spin_unlock(&pm->pm_tree_lock);
78         return error;
79 }
80
81 void pm_put_page(struct page *page)
82 {
83         page_decref(page);
84 }
85
86 /* Makes sure the index'th page of the mapped object is loaded in the page cache
87  * and returns its location via **pp.  Note this will give you a refcnt'd
88  * reference to the page.  This may block! TODO: (BLK) */
89 int pm_load_page(struct page_map *pm, unsigned long index, struct page **pp)
90 {
91         struct page *page;
92         int error;
93         bool page_was_mapped = TRUE;
94
95         page = pm_find_page(pm, index);
96         while (!page) {
97                 /* kpage_alloc, since we want the page to persist after the proc
98                  * dies (can be used by others, until the inode shuts down). */
99                 if (kpage_alloc(&page))
100                         return -ENOMEM;
101                 /* might want to initialize other things, perhaps in page_alloc() */
102                 atomic_set(&page->pg_flags, 0);
103                 error = pm_insert_page(pm, index, page);
104                 switch (error) {
105                         case 0:
106                                 page_was_mapped = FALSE;
107                                 break;
108                         case -EEXIST:
109                                 /* the page was mapped already (benign race), just get rid of
110                                  * our page and try again (the only case that uses the while) */
111                                 page_decref(page);
112                                 page = pm_find_page(pm, index);
113                                 break;
114                         default:
115                                 /* something is wrong, bail out! */
116                                 page_decref(page);
117                                 return error;
118                 }
119         }
120         assert(page && kref_refcnt(&page->pg_kref));
121         /* At this point, page is a refcnt'd page, and we return the reference.
122          * Also, there's an unlikely race where we're not in the page cache anymore,
123          * and this all is useless work. */
124         *pp = page;
125         /* if the page was in the map, we need to do some checks, and might have to
126          * read in the page later.  If the page was freshly inserted to the pm by
127          * us, we skip this since we are the one doing the readpage(). */
128         if (page_was_mapped) {
129                 /* is it already here and up to date?  if so, we're done */
130                 if (atomic_read(&page->pg_flags) & PG_UPTODATE)
131                         return 0;
132                 /* if not, try to lock the page (could BLOCK) */
133                 lock_page(page);
134                 /* we got it, is our page still in the cache?  check the mapping.  if
135                  * not, start over, perhaps with EAGAIN and outside support */
136                 if (!page->pg_mapping)
137                         panic("Page is not in the mapping!  Haven't implemented this!");
138                 /* double check, are we up to date?  if so, we're done */
139                 if (atomic_read(&page->pg_flags) & PG_UPTODATE) {
140                         unlock_page(page);
141                         return 0;
142                 }
143         }
144         /* if we're here, the page is locked by us, and it needs to be read in */
145         assert(page->pg_mapping == pm);
146         /* Readpage will block internally, returning when it is done */
147         error = pm->pm_op->readpage(pm, page);
148         assert(!error);
149         /* Unlock, since we're done with the page and it is up to date */
150         unlock_page(page);
151         assert(atomic_read(&page->pg_flags) & PG_UPTODATE);
152         return 0;
153 }
154
155 void print_page_map_info(struct page_map *pm)
156 {
157         struct vm_region *vmr_i;
158         printk("Page Map %p\n", pm);
159         printk("\tNum pages: %lu\n", pm->pm_num_pages);
160         spin_lock(&pm->pm_lock);
161         TAILQ_FOREACH(vmr_i, &pm->pm_vmrs, vm_pm_link) {
162                 printk("\tVMR proc %d: (%p - %p): 0x%08x, 0x%08x, %p, %p\n",
163                        vmr_i->vm_proc->pid, vmr_i->vm_base, vmr_i->vm_end,
164                        vmr_i->vm_prot, vmr_i->vm_flags, vmr_i->vm_file, vmr_i->vm_foff);
165         }
166         spin_unlock(&pm->pm_lock);
167 }