The Pedigree Project 0.1
storage-page-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/TargetInfo.h"
10#include "pedigree/kernel/machine/Disk.h"
11#include "pedigree/kernel/utilities/Cache.h"
12#include "pedigree/kernel/utilities/utility.h"
13
14namespace {
15constexpr size_t PageSize = TargetInfo::getPageSize();
16constexpr size_t TerminalBytes = 512;
17static_assert(PageSize >= TerminalBytes, "disk regression requires one sector per target page");
18
19bool cacheRangeGeometry() {
20 constexpr uintptr_t EditingKey = 0x57A6000;
21 constexpr uintptr_t PublishedKey = EditingKey + (4 * PageSize);
22 Cache cache;
23
24 bool alreadyExisted = true;
25 const uintptr_t invalid = cache.insert(EditingKey, PageSize + TerminalBytes, &alreadyExisted);
26 const bool insertionRejected = !invalid && !alreadyExisted && !cache.exists(EditingKey, PageSize);
27
28 const uintptr_t editing = cache.insert(EditingKey);
29 cache.markNoLongerEditing(EditingKey, PageSize + TerminalBytes);
30 const bool invalidPublishLeftEditing = editing && cache.discardEditing(EditingKey);
31
32 const uintptr_t published = cache.insert(PublishedKey);
33 cache.markNoLongerEditing(PublishedKey);
34 cache.markEditing(PublishedKey, PageSize + TerminalBytes);
35 const bool invalidEditLeftPublished = published && !cache.discardEditing(PublishedKey);
36 cache.empty();
37
38 const bool passed = insertionRejected && invalidPublishLeftEditing && invalidEditLeftPublished;
39 if (passed) {
40 NOTICE("HOSTED-WAIT-TEST: PASS cache-range-geometry");
41 } else {
42 ERROR(
43 "HOSTED-WAIT-TEST: FAIL cache-range-geometry: a partial cache range "
44 "changed cache state");
45 }
46 return passed;
47}
48
49class SequenceDisk final : public Disk {
50 public:
51 static constexpr size_t DataSize = (2 * PageSize) + TerminalBytes;
52
53 SequenceDisk() : m_Data(new uint8_t[DataSize]), m_BalanceError(false) {
54 for (size_t i = 0; i < DataSize; ++i) {
55 m_Data[i] = static_cast<uint8_t>((i * 31) ^ (i >> 3));
56 }
57 ByteSet(m_References, 0, sizeof(m_References));
58 }
59
60 ~SequenceDisk() override {
61 delete[] m_Data;
62 }
63
64 BufferView read(uint64_t location) override {
65 if (location >= DataSize) {
66 return BufferView();
67 }
68
69 const uint64_t page = location - (location % PageSize);
70 const size_t offset = location - page;
71 const size_t validLength = PageSize < (DataSize - page) ? PageSize : (DataSize - page);
72 ++m_References[page / PageSize];
73 return BufferView(m_Data + location, validLength - offset);
74 }
75
76 size_t getSize() const override {
77 return DataSize;
78 }
79
80 size_t getBlockSize() const override {
81 return TerminalBytes;
82 }
83
84 bool pin(uint64_t location) override {
85 if (location >= DataSize) {
86 return false;
87 }
88 ++m_References[location / PageSize];
89 return true;
90 }
91
92 void unpin(uint64_t location) override {
93 if (location >= DataSize || !m_References[location / PageSize]) {
94 m_BalanceError = true;
95 return;
96 }
97 --m_References[location / PageSize];
98 }
99
100 const uint8_t* data() const {
101 return m_Data;
102 }
103
104 bool balanced() const {
105 return !m_BalanceError && !m_References[0] && !m_References[1] && !m_References[2];
106 }
107
108 private:
109 uint8_t* m_Data;
110 size_t m_References[3];
111 bool m_BalanceError;
112};
113
114bool diskViewSequencePageSpan() {
115 constexpr size_t ReadLength = 2 * PageSize;
116 SequenceDisk disk;
117 BufferView storage[3];
118 BufferViewSequence views(storage, 3);
119 uint8_t* copied = new uint8_t[ReadLength];
120
121 const bool read = disk.readViews(TerminalBytes, ReadLength, views);
122 const bool geometry = read && views.count() == 3 &&
123 views[0].size() == (PageSize - TerminalBytes) &&
124 views[1].size() == PageSize && views[2].size() == TerminalBytes;
125 const bool copiedAll = geometry && views.copyTo(copied, ReadLength) &&
126 !MemoryCompare(copied, disk.data() + TerminalBytes, ReadLength);
127 if (read) {
128 disk.unpinViews(TerminalBytes, views);
129 }
130 const bool released = disk.balanced() && views.empty();
131
132 BufferView shortStorage[2];
133 BufferViewSequence shortViews(shortStorage, 2);
134 const bool capacityFailure = !disk.readViews(TerminalBytes, ReadLength, shortViews) &&
135 shortViews.empty() && disk.balanced();
136 delete[] copied;
137
138 const bool passed = copiedAll && released && capacityFailure;
139 if (passed) {
140 NOTICE("HOSTED-WAIT-TEST: PASS disk-view-sequence-page-span");
141 } else {
142 ERROR(
143 "HOSTED-WAIT-TEST: FAIL disk-view-sequence-page-span: a multi-page "
144 "read lost bounds or cache ownership");
145 }
146 return passed;
147}
148} // namespace
149
150bool runHostedStoragePageRegressions() {
151 return cacheRangeGeometry() && diskViewSequencePageSpan();
152}
Definition Cache.h:207
Definition Disk.h:35
virtual BufferView read(uint64_t location)
Definition Disk.cc:163
virtual size_t getSize() const
Gets the size of the disk.
Definition Disk.cc:344
virtual void unpin(uint64_t location)=0
virtual MUST_USE_RESULT bool pin(uint64_t location)=0
Pins a cache page.
virtual size_t getBlockSize() const
Gets the preferred I/O extent of the disk.
Definition Disk.cc:348
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40