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
17struct _flush_and_drop_data
18{
19 inode_cache_t *icache;
20 bool should_drop_page;
21 long ret;
22};
23
24static 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
54long 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
76long 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
84PtrResult<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
106PtrResult<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
111ssize_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
140ssize_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