84fab400ff6eb0b47c28cf8f2f09169c0fe79f18
[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 /* Initializes a PM.  Host should be an *inode or a *bdev (doesn't matter).  The
16  * reference this stores is uncounted. */
17 void pm_init(struct page_map *pm, struct page_map_operations *op, void *host)
18 {
19         pm->pm_bdev = host;                                             /* note the uncounted ref */
20         radix_tree_init(&pm->pm_tree);
21         spinlock_init(&pm->pm_tree_lock);
22         pm->pm_num_pages = 0;                                   /* no pages in a new pm */
23         pm->pm_op = op;
24         pm->pm_flags = 0;
25 }
26
27 /* Looks up the index'th page in the page map, returning an incref'd reference,
28  * or 0 if it was not in the map. */
29 struct page *pm_find_page(struct page_map *pm, unsigned long index)
30 {
31         spin_lock(&pm->pm_tree_lock);
32         struct page *page = (struct page*)radix_lookup(&pm->pm_tree, index);
33         if (page)
34                 page_incref(page);
35         spin_unlock(&pm->pm_tree_lock);
36         return page;
37 }
38
39 /* Attempts to insert the page into the page_map, returns 0 for success, or an
40  * error code if there was one already (EEXIST) or we ran out of memory
41  * (ENOMEM).  On success, this will preemptively lock the page, and will also
42  * store a reference to the page in the pm. */
43 int pm_insert_page(struct page_map *pm, unsigned long index, struct page *page)
44 {
45         int error = 0;
46         spin_lock(&pm->pm_tree_lock);
47         error = radix_insert(&pm->pm_tree, index, page);
48         if (!error) {
49                 page_incref(page);
50                 page->pg_flags |= PG_LOCKED | PG_BUFFER;
51                 page->pg_sem.nr_signals = 0;            /* ensure others will block */
52                 page->pg_mapping = pm;
53                 page->pg_index = index;
54                 pm->pm_num_pages++;
55         }
56         spin_unlock(&pm->pm_tree_lock);
57         return error;
58 }
59
60 /* Removes the page, including its reference.  Not sure yet what interface we
61  * want to this (pm and index or page), and this has never been used.  There are
62  * also issues with when you want to call this, since a page in the cache may be
63  * mmap'd by someone else. */
64 int pm_remove_page(struct page_map *pm, struct page *page)
65 {
66         void *retval;
67         warn("pm_remove_page() hasn't been thought through or tested.");
68         /* TODO: check for dirty pages, don't let them be removed right away.  Need
69          * to schedule them for writeback, and then remove them later (callback).
70          * Also, need to be careful - anyone holding a reference to a page can dirty
71          * it concurrently. */
72         spin_lock(&pm->pm_tree_lock);
73         retval = radix_delete(&pm->pm_tree, page->pg_index);
74         spin_unlock(&pm->pm_tree_lock);
75         assert(retval == (void*)page);
76         page_decref(page);
77         pm->pm_num_pages--;
78         return 0;
79 }
80
81 /* Makes sure the index'th page of the mapped object is loaded in the page cache
82  * and returns its location via **pp.  Note this will give you a refcnt'd
83  * reference to the page.  This may block! TODO: (BLK) */
84 int pm_load_page(struct page_map *pm, unsigned long index, struct page **pp)
85 {
86         struct page *page;
87         int error;
88         bool page_was_mapped = TRUE;
89
90         page = pm_find_page(pm, index);
91         while (!page) {
92                 /* kpage_alloc, since we want the page to persist after the proc
93                  * dies (can be used by others, until the inode shuts down). */
94                 if (kpage_alloc(&page))
95                         return -ENOMEM;
96                 /* might want to initialize other things, perhaps in page_alloc() */
97                 page->pg_flags = 0;
98                 error = pm_insert_page(pm, index, page);
99                 switch (error) {
100                         case 0:
101                                 page_was_mapped = FALSE;
102                                 break;
103                         case -EEXIST:
104                                 /* the page was mapped already (benign race), just get rid of
105                                  * our page and try again (the only case that uses the while) */
106                                 page_decref(page);
107                                 page = pm_find_page(pm, index);
108                                 break;
109                         default:
110                                 /* something is wrong, bail out! */
111                                 page_decref(page);
112                                 return error;
113                 }
114         }
115         assert(page && kref_refcnt(&page->pg_kref));
116         /* At this point, page is a refcnt'd page, and we return the reference.
117          * Also, there's an unlikely race where we're not in the page cache anymore,
118          * and this all is useless work. */
119         *pp = page;
120         /* if the page was in the map, we need to do some checks, and might have to
121          * read in the page later.  If the page was freshly inserted to the pm by
122          * us, we skip this since we are the one doing the readpage(). */
123         if (page_was_mapped) {
124                 /* is it already here and up to date?  if so, we're done */
125                 if (page->pg_flags & PG_UPTODATE)
126                         return 0;
127                 /* if not, try to lock the page (could BLOCK) */
128                 lock_page(page);
129                 /* we got it, is our page still in the cache?  check the mapping.  if
130                  * not, start over, perhaps with EAGAIN and outside support */
131                 if (!page->pg_mapping)
132                         panic("Page is not in the mapping!  Haven't implemented this!");
133                 /* double check, are we up to date?  if so, we're done */
134                 if (page->pg_flags & PG_UPTODATE) {
135                         unlock_page(page);
136                         return 0;
137                 }
138         }
139         /* if we're here, the page is locked by us, and it needs to be read in */
140         assert(page->pg_mapping == pm);
141         /* Readpage will block internally, returning when it is done */
142         error = pm->pm_op->readpage(pm, page);
143         assert(!error);
144         /* Unlock, since we're done with the page and it is up to date */
145         unlock_page(page);
146         assert(page->pg_flags & PG_UPTODATE);
147         return 0;
148 }