1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include "mos/filesystem/page_cache.hpp" |
4 | |
5 | #include "mos/filesystem/vfs_types.hpp" |
6 | #include "mos/filesystem/vfs_utils.hpp" |
7 | #include "mos/mm/mm.hpp" |
8 | #include "mos/mm/mmstat.hpp" |
9 | #include "mos/mm/physical/pmm.hpp" |
10 | #include "mos/syslog/printk.hpp" |
11 | |
12 | #include <algorithm> |
13 | #include <mos/mos_global.h> |
14 | #include <mos_stdlib.hpp> |
15 | #include <mos_string.hpp> |
16 | |
17 | struct _flush_and_drop_data |
18 | { |
19 | inode_cache_t *icache; |
20 | bool should_drop_page; |
21 | long ret; |
22 | }; |
23 | |
24 | static bool do_flush_and_drop_cached_page(const size_t pgoff, phyframe_t *page, _flush_and_drop_data *fdd) |
25 | { |
26 | // key = page number, value = phyframe_t *, data = _flush_and_drop_data * |
27 | inode_cache_t *const icache = fdd->icache; |
28 | const bool drop_page = fdd->should_drop_page; |
29 | |
30 | // !! TODO |
31 | // !! currently we have no reliable way to track if a page is dirty |
32 | // !! so we always flush it |
33 | // !! this causes performance issues, but it's simple and works for now |
34 | // if (!page->pagecache.dirty) |
35 | |
36 | long ret = 0; |
37 | if (icache->ops->flush_page) |
38 | ret = icache->ops->flush_page(icache, pgoff, page); |
39 | else |
40 | ret = simple_flush_page_discard_data(icache, pgoff, page); |
41 | |
42 | if (!IS_ERR_VALUE(ret) && drop_page) |
43 | { |
44 | // only when the page was successfully flushed |
45 | icache->pages.remove(key: pgoff); |
46 | mmstat_dec1(MEM_PAGECACHE); |
47 | pmm_unref_one(page); |
48 | } |
49 | |
50 | fdd->ret = ret; |
51 | return ret == 0; |
52 | } |
53 | |
54 | long pagecache_flush_or_drop(inode_cache_t *icache, off_t pgoff, size_t npages, bool drop_page) |
55 | { |
56 | struct _flush_and_drop_data data = { .icache = icache, .should_drop_page = drop_page, .ret = 0 }; |
57 | |
58 | long ret = 0; |
59 | for (size_t i = 0; i < npages; i++) |
60 | { |
61 | auto ppage = icache->pages.get(key: pgoff + i); |
62 | if (!ppage.has_value()) |
63 | continue; |
64 | |
65 | do_flush_and_drop_cached_page(pgoff: pgoff + i, page: *ppage, fdd: &data); |
66 | |
67 | if (data.ret != 0) |
68 | { |
69 | ret = data.ret; |
70 | break; |
71 | } |
72 | } |
73 | return ret; |
74 | } |
75 | |
76 | long pagecache_flush_or_drop_all(inode_cache_t *icache, bool drop_page) |
77 | { |
78 | struct _flush_and_drop_data data = { .icache = icache, .should_drop_page = drop_page, .ret = 0 }; |
79 | for (const auto &[pgoff, page] : icache->pages) |
80 | do_flush_and_drop_cached_page(pgoff, page, fdd: &data); |
81 | return data.ret; |
82 | } |
83 | |
84 | PtrResult<phyframe_t> pagecache_get_page_for_read(inode_cache_t *cache, off_t pgoff) |
85 | { |
86 | // fast path |
87 | { |
88 | const auto page = cache->pages.get(key: pgoff); |
89 | if (page) |
90 | return *page; |
91 | } |
92 | |
93 | if (!cache->ops) |
94 | return -EIO; |
95 | |
96 | MOS_ASSERT_X(cache->ops && cache->ops->fill_cache, "no page cache ops for inode %p" , (void *) cache->owner); |
97 | const auto newPage = cache->ops->fill_cache(cache, pgoff); |
98 | if (newPage.isErr()) |
99 | return newPage; |
100 | |
101 | mmstat_inc1(MEM_PAGECACHE); |
102 | cache->pages.insert(key: pgoff, value: newPage.get()); |
103 | return newPage; |
104 | } |
105 | |
106 | PtrResult<phyframe_t> pagecache_get_page_for_write(inode_cache_t *cache, off_t pgoff) |
107 | { |
108 | return pagecache_get_page_for_read(cache, pgoff); |
109 | } |
110 | |
111 | ssize_t vfs_read_pagecache(inode_cache_t *icache, void *buf, size_t size, off_t offset) |
112 | { |
113 | mutex_acquire(mutex: &icache->lock); |
114 | size_t bytes_read = 0; |
115 | size_t bytes_left = size; |
116 | while (bytes_left > 0) |
117 | { |
118 | // bytes to copy from the current page |
119 | const size_t inpage_offset = offset % MOS_PAGE_SIZE; |
120 | const size_t inpage_size = std::min(MOS_PAGE_SIZE - inpage_offset, b: bytes_left); // in case we're at the end of the file, |
121 | |
122 | auto page = pagecache_get_page_for_read(cache: icache, pgoff: offset / MOS_PAGE_SIZE); // the initial page |
123 | if (page.isErr()) |
124 | { |
125 | mutex_release(mutex: &icache->lock); |
126 | return page.getErr(); |
127 | } |
128 | |
129 | memcpy(dest: (char *) buf + bytes_read, src: (void *) (phyframe_va(page.get()) + inpage_offset), n: inpage_size); |
130 | |
131 | bytes_read += inpage_size; |
132 | bytes_left -= inpage_size; |
133 | offset += inpage_size; |
134 | } |
135 | |
136 | mutex_release(mutex: &icache->lock); |
137 | return bytes_read; |
138 | } |
139 | |
140 | ssize_t vfs_write_pagecache(inode_cache_t *icache, const void *buf, size_t total_size, off_t offset) |
141 | { |
142 | const inode_cache_ops_t *ops = icache->ops; |
143 | MOS_ASSERT_X(ops, "no page cache ops for inode %p" , (void *) icache->owner); |
144 | |
145 | mutex_acquire(mutex: &icache->lock); |
146 | |
147 | size_t bytes_written = 0; |
148 | size_t bytes_left = total_size; |
149 | while (bytes_left > 0) |
150 | { |
151 | // bytes to copy to the current page |
152 | const size_t inpage_offset = offset % MOS_PAGE_SIZE; |
153 | const size_t inpage_size = std::min(MOS_PAGE_SIZE - inpage_offset, b: bytes_left); // in case we're at the end of the file, |
154 | |
155 | void *private_data; |
156 | phyframe_t *page; |
157 | const bool can_write = ops->page_write_begin(icache, offset, inpage_size, &page, &private_data); |
158 | if (!can_write) |
159 | { |
160 | pr_warn("page_write_begin failed" ); |
161 | mutex_release(mutex: &icache->lock); |
162 | return -EIO; |
163 | } |
164 | |
165 | memcpy(dest: (char *) (phyframe_va(page) + inpage_offset), src: (char *) buf + bytes_written, n: inpage_size); |
166 | ops->page_write_end(icache, offset, inpage_size, page, private_data); |
167 | |
168 | bytes_written += inpage_size; |
169 | bytes_left -= inpage_size; |
170 | offset += inpage_size; |
171 | } |
172 | |
173 | mutex_release(mutex: &icache->lock); |
174 | return bytes_written; |
175 | } |
176 | |