| 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 | |