The Pedigree Project 0.1
VirtualAddressSpace-remap.cc
1#include "pedigree/kernel/process/Process.h"
2#include "pedigree/kernel/process/Thread.h"
3#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
4#include "pedigree/kernel/processor/ProcessorInformation.h"
5#include "pedigree/kernel/utilities/Pointers.h"
6
7#include "VirtualAddressSpace-internal.h"
8#include "VirtualAddressSpace.h"
9
10namespace {
11constexpr size_t PageSize = 4096;
12constexpr uint64_t LeafFlags = PAGE_PRESENT | PAGE_NO_ACCESS | PAGE_SWAPPED;
13
14size_t tableCapacity(uintptr_t base, size_t length) {
15 if (!length) {
16 return 0;
17 }
18 const uintptr_t last = base + length - 1;
19 return ((last >> 21) - (base >> 21) + 1) + ((last >> 30) - (base >> 30) + 1) +
20 ((last >> 39) - (base >> 39) + 1);
21}
22
23bool permitAllocation(ssize_t& remaining) {
24 if (remaining < 0) {
25 return true;
26 }
27 if (!remaining) {
28 return false;
29 }
30 --remaining;
31 return true;
32}
33} // namespace
34
36 public:
37 using Status = VirtualAddressSpace::RemapStatus;
40
42 : m_Space(space),
43 m_Request(request),
44 m_Moved(request.source != request.destination),
45 m_Preserved(request.oldLength < request.newLength ? request.oldLength : request.newLength),
46 m_Detached(),
47 m_AllowedVictim(),
48 m_Spares(),
49 m_SpareCount(0),
50 m_UsedSpares(0),
51 m_Committed(false) {}
52
53 ~X64PreparedPageRemap() override {
54 for (size_t i = m_UsedSpares; i < m_SpareCount; ++i) {
55 PhysicalMemoryManager::instance().freePage(m_Spares.get()[i]);
56 }
57 }
58
59 Status prepare(ssize_t& remaining) {
60 const size_t destinationPages = m_Moved ? m_Request.newLength / PageSize : 0;
61 const size_t count = (m_Request.oldLength - m_Preserved) / PageSize + destinationPages;
62 if (count) {
63 if (!permitAllocation(remaining)) {
64 return Status::NoMemory;
65 }
66 m_Detached = UniqueArray<Detached>::allocate(count);
67 if (!m_Detached) {
68 return Status::NoMemory;
69 }
70 }
71 if (destinationPages) {
72 if (!permitAllocation(remaining)) {
73 return Status::NoMemory;
74 }
75 m_AllowedVictim = UniqueArray<unsigned char>::allocate(destinationPages);
76 if (!m_AllowedVictim) {
77 return Status::NoMemory;
78 }
79 ByteSet(m_AllowedVictim.get(), 0, destinationPages);
80 }
81 for (size_t i = 0; i < m_Request.victimCount; ++i) {
82 const auto& range = m_Request.victims[i];
83 if (!m_Moved || !m_Request.replace || !range.length || (range.base % PageSize) ||
84 (range.length % PageSize) || range.base < m_Request.destination ||
85 range.base - m_Request.destination > m_Request.newLength ||
86 range.length > m_Request.newLength - (range.base - m_Request.destination)) {
87 return Status::InvalidRange;
88 }
89 const size_t first = (range.base - m_Request.destination) / PageSize;
90 for (size_t j = 0; j < range.length / PageSize; ++j) {
91 if (m_AllowedVictim.get()[first + j]) {
92 return Status::InvalidRange;
93 }
94 m_AllowedVictim.get()[first + j] = 1;
95 }
96 }
97 m_Request.victims = nullptr;
98 m_Request.victimCount = 0;
99 const size_t spares = m_Moved ? tableCapacity(m_Request.destination, m_Preserved) : 0;
100 if (spares) {
101 if (!permitAllocation(remaining)) {
102 return Status::NoMemory;
103 }
105 if (!m_Spares) {
106 return Status::NoMemory;
107 }
108 for (size_t i = 0; i < spares; ++i) {
109 if (!permitAllocation(remaining)) {
110 return Status::NoMemory;
111 }
112 const auto page = PhysicalMemoryManager::instance().allocatePage();
113 if (!page) {
114 return Status::NoMemory;
115 }
116 m_Spares.get()[m_SpareCount++] = page;
117 ByteSet(physicalAddress(reinterpret_cast<void*>(page)), 0, PageSize);
118 }
119 }
120 return Status::Success;
121 }
122
123 Status commit(VirtualAddressSpace::RemapAdmission admission, void* context) override {
124 if (m_Committed || !admission) {
125 return Status::InvalidRange;
126 }
127 size_t detachedCount = 0, removedPages = 0;
128 {
130 mutation.lock(m_Space.m_Lock);
131 for (size_t offset = 0; offset < m_Request.oldLength; offset += PageSize) {
132 uint64_t* leaf = nullptr;
133 const auto status = lookup(m_Request.source + offset, leaf);
134 if (status != Status::Success) {
135 return status;
136 }
137 }
138 for (size_t offset = 0; offset < m_Request.newLength; offset += PageSize) {
139 uint64_t* leaf = nullptr;
140 const auto status = lookup(m_Request.destination + offset, leaf);
141 if (status != Status::Success) {
142 return status;
143 }
144 if (!m_Moved && offset < m_Request.oldLength) {
145 continue;
146 }
147 if (leaf && (*leaf & LeafFlags) &&
148 (!m_Moved || !m_Request.replace || !m_AllowedVictim.get()[offset / PageSize])) {
149 return Status::InvalidRange;
150 }
151 }
152 // Nothing below can allocate or fail. In particular, a concurrent CoW
153 // replacement is now excluded and its latest leaf is the transferred one.
154 if (!admission(context)) {
155 return Status::Retry;
156 }
157 for (size_t offset = m_Preserved; offset < m_Request.oldLength; offset += PageSize) {
158 removedPages += detach(m_Request.source + offset, m_Detached.get()[detachedCount++]);
159 }
160 if (m_Moved) {
161 for (size_t offset = 0; offset < m_Request.newLength; offset += PageSize) {
162 removedPages += detach(m_Request.destination + offset, m_Detached.get()[detachedCount++]);
163 }
164 for (size_t offset = 0; offset < m_Preserved; offset += PageSize) {
165 uint64_t* source = nullptr;
166 lookup(m_Request.source + offset, source);
167 if (!source || !(*source & LeafFlags)) {
168 continue;
169 }
170 uint64_t* destination = ensureLeaf(m_Request.destination + offset);
171 const uint64_t value = __atomic_exchange_n(source, uint64_t{0}, __ATOMIC_ACQ_REL);
172 __atomic_store_n(destination, value, __ATOMIC_RELEASE);
173 }
174 }
175 for (size_t offset = 0; offset < m_Request.oldLength; offset += PageSize) {
176 if (!m_Space.invalidateMapping(reinterpret_cast<void*>(m_Request.source + offset),
177 mutation)) {
178 mutation.panicInvalidationFailure();
179 }
180 }
181 if (m_Moved || m_Request.newLength > m_Request.oldLength) {
182 const size_t begin = m_Moved ? 0 : m_Request.oldLength;
183 for (size_t offset = begin; offset < m_Request.newLength; offset += PageSize) {
184 if (!m_Space.invalidateMapping(reinterpret_cast<void*>(m_Request.destination + offset),
185 mutation)) {
186 mutation.panicInvalidationFailure();
187 }
188 }
189 }
190 m_Committed = true;
191 }
192 Thread* thread = Processor::information().getCurrentThread();
193 if (thread && thread->getParent()) {
194 Process* process = thread->getParent();
195 if (process->getAddressSpace() == &m_Space)
196 process = process->addressSpaceOwner();
197 process->trackPages(-static_cast<ssize_t>(removedPages), 0, 0);
198 }
199 return Status::Success;
200 }
201
202 const Detached* detachedPages() const override {
203 return m_Committed ? m_Detached.get() : nullptr;
204 }
205 size_t detachedPageCount() const override {
206 return m_Committed ? (m_Request.oldLength - m_Preserved) / PageSize +
207 (m_Moved ? m_Request.newLength / PageSize : 0)
208 : 0;
209 }
210
211 private:
212 Status lookup(uintptr_t address, uint64_t*& leaf) {
213 leaf = nullptr;
214 uint64_t table = m_Space.m_PhysicalPML4;
215 for (size_t shift = 39; shift > 12; shift -= 9) {
216 uint64_t* entry = TABLE_ENTRY(table, (address >> shift) & 511);
217 if (!(*entry & PAGE_PRESENT)) {
218 return Status::Success;
219 }
220 if (*entry & PAGE_2MB) {
221 return Status::Unsupported;
222 }
223 table = PAGE_GET_PHYSICAL_ADDRESS(entry);
224 }
225 leaf = TABLE_ENTRY(table, (address >> 12) & 511);
226 if ((*leaf & PAGE_SWAPPED) || ((*leaf & LeafFlags) && !(*leaf & PAGE_USER))) {
227 return Status::Unsupported;
228 }
229 return Status::Success;
230 }
231
232 bool detach(uintptr_t address, Detached& result) {
233 result = {address, 0, 0, false};
234 uint64_t* leaf = nullptr;
235 lookup(address, leaf);
236 if (!leaf || !(*leaf & LeafFlags)) {
237 return false;
238 }
239 const uint64_t value = __atomic_exchange_n(leaf, uint64_t{0}, __ATOMIC_ACQ_REL);
240 result.physical = value & ~0x8780000000000FFFULL;
241 result.flags = m_Space.fromFlags(value & 0x8780000000000FFFULL, true);
242 result.mapped = true;
243 return true;
244 }
245
246 uint64_t* ensureLeaf(uintptr_t address) {
247 uint64_t table = m_Space.m_PhysicalPML4;
248 for (size_t shift = 39; shift > 12; shift -= 9) {
249 uint64_t* entry = TABLE_ENTRY(table, (address >> shift) & 511);
250 if (!(*entry & PAGE_PRESENT)) {
251 assert(m_UsedSpares < m_SpareCount);
252 *entry = m_Spares.get()[m_UsedSpares++] | PAGE_PRESENT | PAGE_WRITE | PAGE_USER;
253 }
254 table = PAGE_GET_PHYSICAL_ADDRESS(entry);
255 }
256 return TABLE_ENTRY(table, (address >> 12) & 511);
257 }
258
259 X64VirtualAddressSpace& m_Space;
260 Request m_Request;
261 bool m_Moved;
262 size_t m_Preserved;
263 UniqueArray<Detached> m_Detached;
264 UniqueArray<unsigned char> m_AllowedVictim;
266 size_t m_SpareCount, m_UsedSpares;
267 bool m_Committed;
268};
269
270VirtualAddressSpace::RemapStatus X64VirtualAddressSpace::prepareRemap(
272 plan.reset();
273 const auto validRange = [this](uintptr_t base, size_t length) {
274 if (!length || (base % PageSize) || (length % PageSize) || length > ~uintptr_t{0} - base) {
275 return false;
276 }
277 const uintptr_t end = base + length;
278 return (base >= getUserStart() && end <= getUserReservedStart()) ||
279 (base >= getDynamicStart() && end <= getDynamicEnd());
280 };
281 if (m_bKernelSpace || !validRange(request.source, request.oldLength) ||
282 !validRange(request.destination, request.newLength) ||
283 (request.victimCount && !request.victims)) {
284 return RemapStatus::InvalidRange;
285 }
286 if (request.oldLength / PageSize > MaximumRemapPages ||
287 request.newLength / PageSize > MaximumRemapPages || request.victimCount > MaximumRemapPages) {
288 return RemapStatus::NoMemory;
289 }
290 if (request.source != request.destination &&
291 request.source < request.destination + request.newLength &&
292 request.destination < request.source + request.oldLength) {
293 return RemapStatus::InvalidRange;
294 }
295 ssize_t remaining =
296 __atomic_exchange_n(&m_RemapPreparationFailure, ssize_t{-1}, __ATOMIC_ACQ_REL);
297 if (!permitAllocation(remaining)) {
298 return RemapStatus::NoMemory;
299 }
300 auto* prepared = new X64PreparedPageRemap(*this, request);
301 if (!prepared) {
302 return RemapStatus::NoMemory;
303 }
304 auto owner = UniquePointer<PreparedPageRemap>::adopt(prepared);
305 const auto status = prepared->prepare(remaining);
306 if (status == RemapStatus::Success) {
307 plan = pedigree_std::move(owner);
308 }
309 return status;
310}
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
VirtualAddressSpace * getAddressSpace()
Definition Process.h:478
Process * addressSpaceOwner()
Definition Process.h:493
static ProcessorInformation & information()
Process * getParent() const
Definition Thread.h:338
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
Status commit(VirtualAddressSpace::RemapAdmission admission, void *context) override
size_t fromFlags(uint64_t Flags, bool bFinal=false) const PURE
bool invalidateMapping(void *virtualAddress, X64MappingMutationScope &mutation)
RemapStatus prepareRemap(const PageRemapRequest &request, UniquePointer< PreparedPageRemap > &plan) override
uintptr_t physicalAddress(physical_uintptr_t address) PURE
Definition utils.h:39