7-ps2_keyboard.md and 8-multitasking.md (#29)
[lunaix-os.git] / lunaix-os / kernel / fs / pcache.c
1 #include <klibc/string.h>
2 #include <lunaix/ds/btrie.h>
3 #include <lunaix/fs.h>
4 #include <lunaix/mm/pmm.h>
5 #include <lunaix/mm/valloc.h>
6 #include <lunaix/mm/vmm.h>
7 #include <lunaix/spike.h>
8
9 #define PCACHE_DIRTY 0x1
10
11 static struct lru_zone* pcache_zone;
12
13 static int
14 __pcache_try_evict(struct lru_node* obj)
15 {
16     struct pcache_pg* page = container_of(obj, struct pcache_pg, lru);
17     pcache_invalidate(page->holder, page);
18     return 1;
19 }
20
21 static void
22 pcache_free_page(void* va)
23 {
24     ptr_t pa = vmm_del_mapping(VMS_SELF, (ptr_t)va);
25     pmm_free_page(pa);
26 }
27
28 static void*
29 pcache_alloc_page()
30 {
31     int i = 0;
32     ptr_t pp = pmm_alloc_page(0), va = 0;
33
34     if (!pp) {
35         return NULL;
36     }
37
38     if (!(va = (ptr_t)vmap(pp, PAGE_SIZE, KERNEL_DATA))) {
39         pmm_free_page(pp);
40         return NULL;
41     }
42
43     return (void*)va;
44 }
45
46 void
47 pcache_init(struct pcache* pcache)
48 {
49     btrie_init(&pcache->tree, PAGE_SHIFT);
50     llist_init_head(&pcache->dirty);
51     llist_init_head(&pcache->pages);
52
53     pcache_zone = lru_new_zone(__pcache_try_evict);
54 }
55
56 void
57 pcache_release_page(struct pcache* pcache, struct pcache_pg* page)
58 {
59     pcache_free_page(page->pg);
60
61     llist_delete(&page->pg_list);
62
63     btrie_remove(&pcache->tree, page->fpos);
64
65     vfree(page);
66
67     pcache->n_pages--;
68 }
69
70 struct pcache_pg*
71 pcache_new_page(struct pcache* pcache, u32_t index)
72 {
73     struct pcache_pg* ppg = vzalloc(sizeof(struct pcache_pg));
74     void* pg = pcache_alloc_page();
75
76     if (!ppg || !pg) {
77         lru_evict_one(pcache_zone);
78         if (!ppg && !(ppg = vzalloc(sizeof(struct pcache_pg)))) {
79             return NULL;
80         }
81
82         if (!pg && !(pg = pcache_alloc_page())) {
83             return NULL;
84         }
85     }
86
87     ppg->pg = pg;
88     ppg->holder = pcache;
89
90     llist_append(&pcache->pages, &ppg->pg_list);
91     btrie_set(&pcache->tree, index, ppg);
92
93     return ppg;
94 }
95
96 void
97 pcache_set_dirty(struct pcache* pcache, struct pcache_pg* pg)
98 {
99     if (!(pg->flags & PCACHE_DIRTY)) {
100         pg->flags |= PCACHE_DIRTY;
101         pcache->n_dirty++;
102         llist_append(&pcache->dirty, &pg->dirty_list);
103     }
104 }
105
106 int
107 pcache_get_page(struct pcache* pcache,
108                 u32_t index,
109                 u32_t* offset,
110                 struct pcache_pg** page)
111 {
112     struct pcache_pg* pg = btrie_get(&pcache->tree, index);
113     int is_new = 0;
114     u32_t mask = ((1 << pcache->tree.truncated) - 1);
115     *offset = index & mask;
116     if (!pg && (pg = pcache_new_page(pcache, index))) {
117         pg->fpos = index & ~mask;
118         pcache->n_pages++;
119         is_new = 1;
120     }
121     if (pg)
122         lru_use_one(pcache_zone, &pg->lru);
123     *page = pg;
124     return is_new;
125 }
126
127 int
128 pcache_write(struct v_inode* inode, void* data, u32_t len, u32_t fpos)
129 {
130     int errno = 0;
131     u32_t pg_off, buf_off = 0;
132     struct pcache* pcache = inode->pg_cache;
133     struct pcache_pg* pg;
134
135     while (buf_off < len && errno >= 0) {
136         u32_t wr_bytes = MIN(PAGE_SIZE - pg_off, len - buf_off);
137
138         int new_page = pcache_get_page(pcache, fpos, &pg_off, &pg);
139
140         if (new_page) {
141             // Filling up the page
142             errno = inode->default_fops->read_page(inode, pg->pg, pg->fpos);
143
144             if (errno < 0) {
145                 break;
146             }
147             if (errno < (int)PAGE_SIZE) {
148                 // EOF
149                 len = MIN(len, buf_off + errno);
150             }
151         } else if (!pg) {
152             errno = inode->default_fops->write(inode, data, wr_bytes, fpos);
153             continue;
154         }
155
156         memcpy(pg->pg + pg_off, (data + buf_off), wr_bytes);
157         pcache_set_dirty(pcache, pg);
158
159         pg->len = pg_off + wr_bytes;
160         buf_off += wr_bytes;
161         fpos += wr_bytes;
162     }
163
164     return errno < 0 ? errno : (int)buf_off;
165 }
166
167 int
168 pcache_read(struct v_inode* inode, void* data, u32_t len, u32_t fpos)
169 {
170     u32_t pg_off, buf_off = 0, new_pg = 0;
171     int errno = 0;
172     struct pcache* pcache = inode->pg_cache;
173     struct pcache_pg* pg;
174
175     while (buf_off < len) {
176         int new_page = pcache_get_page(pcache, fpos, &pg_off, &pg);
177         if (new_page) {
178             // Filling up the page
179             errno = inode->default_fops->read_page(inode, pg->pg, pg->fpos);
180
181             if (errno < 0) {
182                 break;
183             }
184             if (errno < (int)PAGE_SIZE) {
185                 // EOF
186                 len = MIN(len, buf_off + errno);
187             }
188
189             pg->len = errno;
190         } else if (!pg) {
191             errno = inode->default_fops->read(
192               inode, (data + buf_off), len - buf_off, pg->fpos);
193             buf_off = len;
194             break;
195         }
196
197         u32_t rd_bytes = MIN(pg->len - pg_off, len - buf_off);
198
199         if (!rd_bytes)
200             break;
201
202         memcpy((data + buf_off), pg->pg + pg_off, rd_bytes);
203
204         buf_off += rd_bytes;
205         fpos += rd_bytes;
206     }
207
208     return errno < 0 ? errno : (int)buf_off;
209 }
210
211 void
212 pcache_release(struct pcache* pcache)
213 {
214     struct pcache_pg *pos, *n;
215     llist_for_each(pos, n, &pcache->pages, pg_list)
216     {
217         lru_remove(pcache_zone, &pos->lru);
218         vfree(pos);
219     }
220
221     btrie_release(&pcache->tree);
222 }
223
224 int
225 pcache_commit(struct v_inode* inode, struct pcache_pg* page)
226 {
227     if (!(page->flags & PCACHE_DIRTY)) {
228         return 0;
229     }
230
231     int errno = inode->default_fops->write_page(inode, page->pg, page->fpos);
232
233     if (!errno) {
234         page->flags &= ~PCACHE_DIRTY;
235         llist_delete(&page->dirty_list);
236         inode->pg_cache->n_dirty--;
237     }
238
239     return errno;
240 }
241
242 void
243 pcache_commit_all(struct v_inode* inode)
244 {
245     if (!inode->pg_cache) {
246         return;
247     }
248
249     struct pcache* cache = inode->pg_cache;
250     struct pcache_pg *pos, *n;
251
252     llist_for_each(pos, n, &cache->dirty, dirty_list)
253     {
254         pcache_commit(inode, pos);
255     }
256 }
257
258 void
259 pcache_invalidate(struct pcache* pcache, struct pcache_pg* page)
260 {
261     pcache_commit(pcache->master, page);
262     pcache_release_page(pcache, page);
263 }