ZXFoundation™ 26h2
Loading...
Searching...
No Matches
core.cxxm
Go to the documentation of this file.
1/// SPDX-License-Identifier: Apache-2.0
2/// @file arch/s390x/mmu/core.cxxm
3/// @brief MMU engine — transactional, type-safe DAT table mapper.
4
5export module arch.s390x.mmu.core;
6import arch.s390x.mmu.types;
7import arch.s390x.mmu.consts;
8import arch.s390x.mmu.tlb;
9import zxfoundation.base.types;
10import zxfoundation.memory.pmm;
11import zxfoundation.memory.pmm.types;
12import zxfoundation.base.transaction;
13import lib.error;
14import std;
15
16namespace arch::s390x::mmu::detail {
17
18 using namespace zxfoundation::memory;
19
20 from_global(reference) bool g_has_edat1;
21 from_global(reference) bool g_has_edat2;
22
23 /// @brief Build the default PMM request used for DAT table pages.
24 [[nodiscard]] auto default_table_request() noexcept -> pmm::allocation_request;
25
26 /// @brief Fill a freshly-allocated table with empty (invalid) entries.
27 [[nodiscard]] auto init_table(dat_level lvl, u64 phys) noexcept -> ::std::expected<void, ::lib::kernel_error>;
28
29 /// @brief Map a single 4 KiB page using the two-phase protocol.
30 /// @return On success, empty expected.
31 auto map_page_impl(
32 u64 root_phys,
33 dat_level root_level,
34 u64 virt,
35 u64 phys,
36 page_prot prot,
37 const pmm::allocation_request& table_policy
38 ) noexcept -> ::std::expected<void, ::lib::kernel_error>;
39
40 /// @brief Unmap whatever exists at @p virt (4 KiB, 1 MiB, or 2 GiB).
41 /// @return Size of the mapping removed, or kernel_error if nothing was mapped.
42 auto unmap_at_impl(
43 u64 root_phys,
44 dat_level root_level,
45 u64 virt
46 ) noexcept -> ::std::expected<u64, ::lib::kernel_error>;
47
48 /// @brief Walk the page tables to resolve virt → phys.
49 auto walk_impl(
50 u64 root_phys,
51 dat_level root_level,
52 u64 virt
53 ) noexcept -> ::std::expected<u64, ::lib::kernel_error>;
54
55 /// @brief Change protection bits on an existing mapping.
56 auto protect_impl(
57 u64 root_phys,
58 dat_level root_level,
59 u64 virt,
60 page_prot new_prot
61 ) noexcept -> ::std::expected<void, ::lib::kernel_error>;
62
63 /// @brief Map a 1 MiB large page (EDAT-1) via segment entry.
65 u64 root_phys,
66 dat_level root_level,
67 u64 virt,
68 u64 phys,
69 page_prot prot
70 ) noexcept -> ::std::expected<void, ::lib::kernel_error>;
71
72 /// @brief Map a 2 GiB large page (EDAT-2) via region-3 entry.
74 u64 root_phys,
75 dat_level root_level,
76 u64 virt,
77 u64 phys,
78 page_prot prot
79 ) noexcept -> ::std::expected<void, ::lib::kernel_error>;
80
81} // namespace arch::s390x::mmu::detail
82
83export {
84
85namespace arch::s390x::mmu {
86
87 /// @brief Initialize the MMU engine.
88 auto init() noexcept -> void;
89
90 /// @brief Query EDAT-1 (1 MiB large page) availability.
91 [[nodiscard]] auto has_edat1() noexcept -> bool;
92
93 /// @brief Query EDAT-2 (2 GiB large page) availability.
94 [[nodiscard]] auto has_edat2() noexcept -> bool;
95
96 /// @brief Allocate and initialize a CRST table for the given level.
97 template <dat_level Level>
98 requires (Level != dat_level::page)
99 [[nodiscard]] auto crst_alloc() noexcept
100 -> ::std::expected<dat_table<Level>, ::lib::kernel_error>
101 {
102 auto result = zxfoundation::memory::pmm::alloc(
103 CRST_ALLOC_ORDER,
104 zxfoundation::memory::pmm::zone::normal,
105 zxfoundation::memory::pmm::purpose::page_table);
106 if (!result) [[unlikely]]
107 return ::std::unexpected(result.error());
108 const u64 phys = result->phys_base;
109 auto init_result = detail::init_table(Level, phys);
110 if (!init_result) [[unlikely]] {
111 zxfoundation::memory::pmm::folio_active f{
112 phys,
113 CRST_ALLOC_ORDER,
114 zxfoundation::memory::pmm::purpose::page_table,
115 zxfoundation::memory::pmm::zone::normal};
116 zxfoundation::memory::pmm::free(::std::move(f));
117 return ::std::unexpected(init_result.error());
118 }
119 return dat_table<Level>(phys);
120 }
121
122 /// @brief Allocate and initialize a page table.
123 [[nodiscard]] auto pte_table_alloc() noexcept
124 -> ::std::expected<dat_table<dat_level::page>, ::lib::kernel_error>;
125
126 /// @brief Free a previously allocated table.
127 template <dat_level Level>
128 auto table_free(dat_table<Level> tbl) noexcept -> void {
129 if (!tbl.valid()) return;
130 constexpr u32 order = (Level == dat_level::page)
131 ? PTE_ALLOC_ORDER : CRST_ALLOC_ORDER;
132 zxfoundation::memory::pmm::folio_active f{
133 tbl.origin_phys, order,
134 zxfoundation::memory::pmm::purpose::page_table,
135 zxfoundation::memory::pmm::zone::normal};
136 zxfoundation::memory::pmm::free(::std::move(f));
137 }
138
139 /// @brief Build a typed ASCE for a kernel address space.
140 template <dat_level Root>
141 requires (Root != dat_level::page)
142 [[nodiscard]] auto asce_build(dat_table<Root> root) noexcept -> typed_asce<Root> {
143 const u64 val = (root.origin_phys & ASCE_ORIGIN_MASK)
144 | asce_type_code<Root>()
145 | ASCE_LENGTH_MASK;
146 return typed_asce<Root>(val);
147 }
148
149 /// @brief Map a single 4 KiB page. Side-effect-free on failure.
150 /// @pre Caller holds exclusive access to the root table.
151 template <dat_level Root>
152 [[nodiscard]] auto map_page(
153 dat_table<Root> root,
154 u64 virt,
155 u64 phys,
156 page_prot prot
157 ) noexcept -> ::std::expected<void, ::lib::kernel_error> {
158 const auto table_policy = detail::default_table_request();
159 auto result = detail::map_page_impl(
160 root.origin_phys, Root, virt, phys, prot, table_policy);
161 return result;
162 }
163
164 /// @brief Map a single 4 KiB page with explicit page-table allocation placement.
165 /// @param[in] root Root DAT table.
166 /// @param[in] virt Virtual address to map.
167 /// @param[in] phys Physical frame to install.
168 /// @param[in] prot Hardware protection bits.
169 /// @param[in] table_policy PMM allocation request used for newly allocated DAT tables.
170 /// @return Success or kernel error.
171 /// @note Existing child tables are reused; the policy applies only to tables allocated during this map walk.
172 template <dat_level Root>
173 [[nodiscard]] auto map_page_with_policy(
174 dat_table<Root> root,
175 u64 virt,
176 u64 phys,
177 page_prot prot,
178 const zxfoundation::memory::pmm::allocation_request& table_policy
179 ) noexcept -> ::std::expected<void, ::lib::kernel_error> {
180 auto result = detail::map_page_impl(
181 root.origin_phys, Root, virt, phys, prot, table_policy);
182 return result;
183 }
184
185 /// @brief Unmap a single page/large-page at @p virt.
186 /// @return Physical address of the unmapped frame, or kernel_error.
187 /// @pre Caller holds exclusive access to the root table.
188 template <dat_level Root>
189 [[nodiscard]] auto unmap_page(
190 dat_table<Root> root,
191 u64 virt
192 ) noexcept -> ::std::expected<u64, ::lib::kernel_error> {
193 // First resolve the physical address (walk).
194 auto phys_result = detail::walk_impl(
195 root.origin_phys, Root, virt);
196 if (!phys_result)
197 return ::std::unexpected(phys_result.error());
198 const u64 old_phys = *phys_result;
199 // Now unmap.
200 auto size_result = detail::unmap_at_impl(
201 root.origin_phys, Root, virt);
202 if (!size_result)
203 return ::std::unexpected(size_result.error());
204 return old_phys;
205 }
206
207 /// @brief Runtime-dispatched unmap keyed on an ASCE value.
208 /// @param[in] asce_val ASCE value (root table + type + length).
209 /// @param[in] virt Virtual address whose PTE to invalidate.
210 /// @return The physical address of the unmapped frame, or
211 /// kernel_error (not_present if the address was already
212 /// unmapped, invalid_address if the root level is bogus).
213 [[nodiscard]] auto unmap_page_dispatch(
214 u64 asce_val, u64 virt
215 ) noexcept -> ::std::expected<u64, ::lib::kernel_error>;
216
217 /// @brief Walk the page tables to resolve virt → phys.
218 /// @return Physical address corresponding to @p virt, or kernel_error.
219 template <dat_level Root>
220 [[nodiscard]] auto walk(
221 dat_table<Root> root,
222 u64 virt
223 ) noexcept -> ::std::expected<u64, ::lib::kernel_error> {
224 return detail::walk_impl(root.origin_phys, Root, virt);
225 }
226
227 /// @brief Change protection bits on an existing mapping.
228 /// @pre Caller holds exclusive access to the root table.
229 template <dat_level Root>
230 [[nodiscard]] auto protect(
231 dat_table<Root> root,
232 u64 virt,
233 page_prot new_prot
234 ) noexcept -> ::std::expected<void, ::lib::kernel_error> {
235 return detail::protect_impl(
236 root.origin_phys, Root, virt, new_prot);
237 }
238
239 /// @brief Transactional wrapper for batched MMU operations.
240 class mmu_transaction
241 : public zxfoundation::base::transaction<mmu_transaction> {
242 friend class zxfoundation::base::transaction<mmu_transaction>;
243
244 u64 root_phys_{0};
245 dat_level root_level_{dat_level::region_1};
246 u64 asce_val_{0};
247 u64 range_start_{0};
248 u64 range_cursor_{0};
249
250 public:
251 mmu_transaction() noexcept = default;
252
253 mmu_transaction(
254 u64 root_phys,
255 dat_level root_level,
256 u64 asce_val,
257 u64 range_start
258 ) noexcept
259 : root_phys_(root_phys)
260 , root_level_(root_level)
261 , asce_val_(asce_val)
262 , range_start_(range_start)
263 , range_cursor_(range_start)
264 {}
265
266 mmu_transaction(const mmu_transaction&) = delete;
267 auto operator=(const mmu_transaction&) = delete;
268
269 /// @brief Advance the cursor after a successful mapping.
270 auto advance(u64 bytes) noexcept -> void;
271
272 /// @brief Commit the transaction and flush TLB.
273 auto commit_and_flush() noexcept -> void;
274
275 private:
276 /// @brief Rollback: unmap all successfully-mapped entries, then flush.
277 auto on_rollback() const noexcept -> void;
278 };
279
280 /// @brief Map a contiguous range [virt, virt+len) → [phys, phys+len).
281 /// @return Total bytes mapped on success, or kernel_error.
282 /// @pre Caller holds exclusive access to the root table.
283 /// @pre @p virt and @p phys are PAGE_SIZE_4K-aligned.
284 /// @pre @p len is PAGE_SIZE_4K-aligned and > 0.
285 template <dat_level Root>
286 [[nodiscard]] auto map_range(
287 dat_table<Root> root,
288 u64 virt,
289 u64 phys,
290 u64 len,
291 page_prot prot
292 ) noexcept -> ::std::expected<u64, ::lib::kernel_error> {
293 const u64 asce_val = asce_build(root).raw();
294 mmu_transaction txn(root.origin_phys, Root, asce_val, virt);
295
296 u64 va = virt;
297 u64 pa = phys;
298 u64 remaining = len;
299
300 while (remaining > 0) {
301 u64 step;
302
303 // Try 2 GiB large page (EDAT-2).
304 if (detail::g_has_edat2
305 && remaining >= REGION3_SIZE
306 && (va & (REGION3_SIZE - 1)) == 0
307 && (pa & (REGION3_SIZE - 1)) == 0) {
308 auto r = detail::map_large_2g_impl(
309 root.origin_phys, Root, va, pa, prot);
310 if (!r)
311 return ::std::unexpected(r.error());
312 step = REGION3_SIZE;
313 }
314 // Try 1 MiB large page (EDAT-1).
315 else if (detail::g_has_edat1
316 && remaining >= SEGMENT_SIZE
317 && (va & (SEGMENT_SIZE - 1)) == 0
318 && (pa & (SEGMENT_SIZE - 1)) == 0) {
319 auto r = detail::map_large_1m_impl(
320 root.origin_phys, Root, va, pa, prot);
321 if (!r)
322 return ::std::unexpected(r.error());
323 step = SEGMENT_SIZE;
324 }
325 // Fall back to 4 KiB.
326 else {
327 auto r = detail::map_page_impl(
328 root.origin_phys, Root, va, pa, prot);
329 if (!r)
330 return ::std::unexpected(r.error());
331 step = PAGE_SIZE_4K;
332 }
333
334 txn.advance(step);
335 va += step;
336 pa += step;
337 remaining -= step;
338 }
339
340 txn.commit_and_flush();
341 return len;
342 }
343
344 /// @brief Unmap a contiguous range [virt, virt+len).
345 /// @return Total bytes unmapped, or kernel_error.
346 /// @pre Caller holds exclusive access to the root table.
347 template <dat_level Root>
348 [[nodiscard]] auto unmap_range(
349 dat_table<Root> root,
350 u64 virt,
351 u64 len
352 ) noexcept -> ::std::expected<u64, ::lib::kernel_error> {
353 u64 va = virt;
354 u64 remaining = len;
355 u64 unmapped = 0;
356
357 while (remaining > 0) {
358 auto r = detail::unmap_at_impl(
359 root.origin_phys, Root, va);
360 u64 step;
361 if (r) {
362 step = *r;
363 unmapped += step;
364 } else {
365 // Nothing mapped here — skip a page.
366 step = PAGE_SIZE_4K;
367 }
368 va += step;
369 remaining = (remaining >= step) ? remaining - step : 0;
370 }
371
372 // Single IDTE flush for the entire address space.
373 if (unmapped > 0) {
374 const u64 asce_val = asce_build(root).raw();
375 tlb_flush_asce(asce_val);
376 }
377 return unmapped;
378 }
379
380} // namespace arch::s390x::mmu
381
382} // end export
auto init_table(dat_level lvl, u64 phys) noexcept -> ::std::expected< void, ::lib::kernel_error >
Fill a freshly-allocated table with empty (invalid) entries.
Definition core.cxx:57
auto map_large_2g_impl(u64 root_phys, dat_level root_level, u64 virt, u64 phys, page_prot prot) noexcept -> ::std::expected< void, ::lib::kernel_error >
Map a 2 GiB large page (EDAT-2) via region-3 entry.
Definition core.cxx:498
auto protect_impl(u64 root_phys, dat_level root_level, u64 virt, page_prot new_prot) noexcept -> ::std::expected< void, ::lib::kernel_error >
Change protection bits on an existing mapping.
Definition core.cxx:361
auto unmap_at_impl(u64 root_phys, dat_level root_level, u64 virt) noexcept -> ::std::expected< u64, ::lib::kernel_error >
Unmap whatever exists at virt (4 KiB, 1 MiB, or 2 GiB).
Definition core.cxx:261
auto map_page_impl(u64 root_phys, dat_level root_level, u64 virt, u64 phys, page_prot prot, const pmm::allocation_request &table_policy) noexcept -> ::std::expected< void, ::lib::kernel_error >
Map a single 4 KiB page using the two-phase protocol.
Definition core.cxx:132
auto default_table_request() noexcept -> pmm::allocation_request
Build the default PMM request used for DAT table pages.
Definition core.cxx:31
auto map_large_1m_impl(u64 root_phys, dat_level root_level, u64 virt, u64 phys, page_prot prot) noexcept -> ::std::expected< void, ::lib::kernel_error >
Map a 1 MiB large page (EDAT-1) via segment entry.
Definition core.cxx:414
auto walk_impl(u64 root_phys, dat_level root_level, u64 virt) noexcept -> ::std::expected< u64, ::lib::kernel_error >
Walk the page tables to resolve virt → phys.
Definition core.cxx:324