Skip to main content

strat9_kernel/memory/
paging.rs

1//! Virtual Memory Management (Paging) for Strat9-OS
2//!
3//! Uses the `x86_64` crate's `OffsetPageTable` which is designed for the HHDM
4//! (Higher Half Direct Map) pattern : exactly what the UEFI bootloader provides.
5//!
6//! Provides map/unmap/translate operations on the active page table.
7
8use crate::{
9    arch::xshim::{PageTableFlags, PhysAddr, PhysFrame as X86PhysFrame, Size4KiB, VirtAddr},
10    x86_crate_shim::{
11        registers::control::Cr3,
12        structures::paging::{
13            FrameAllocator as X86FrameAllocator, Mapper, OffsetPageTable, Page, PageTable,
14            Translate,
15        },
16    },
17};
18
19use crate::{
20    memory::frame::{FrameAllocOptions, FramePurpose},
21    sync::SpinLock,
22};
23
24/// Wrapper around the buddy allocator implementing the x86_64 crate's `FrameAllocator` trait.
25///
26/// Used by `OffsetPageTable` when it needs a new intermediate page-table node
27/// (PML4 / PDPT / PD / PT).
28///
29/// # Safety invariant: page-table frames MUST be zeroed
30///
31/// The x86_64 CPU page-table walker reads all 512 entries of every
32/// intermediate node it traverses, regardless of which entries are "in use".
33/// If a newly allocated page-table frame contains stale bytes (left behind by
34/// the slab allocator or a previous allocation), any non-zero entry is decoded
35/// as a valid PTE pointing to an arbitrary physical address.  The first fetch
36/// from such an address becomes the new RIP after the Ring 3 transition :
37/// explaining why RIP is non-deterministic across boots.
38///
39/// `BuddyFrameAllocator` enforces zeroing via `FrameAllocOptions::new()
40///  .purpose(FramePurpose::PageTable)` which:
41///
42///  1. Calls the buddy allocator for a raw order-0 frame.
43///  2. CAS-claims the frame via the [`MetaSlot`](crate::memory::MetaSlot) refcount field
44///     (`REFCOUNT_UNUSED` => `1`).
45///  3. Zeros the 4 KiB with a single `ptr::write_bytes` through the HHDM.
46///  4. Sets purpose flags on the [`MetaSlot`](crate::memory::MetaSlot) with `Release` ordering.
47///  5. Stores `refcount = 1` with `Release` ordering so any future reader
48///     that loads the refcount with `Acquire` observes a fully-initialised frame.
49///
50/// This matches the Asterinas OSTD pattern (`FrameAllocOptions` + per-frame
51/// [`MetaSlot`](crate::memory::MetaSlot) with refcount CAS). Metadata lives in
52/// dedicated slots (not in mapped page bytes); see [`get_meta_slot`](crate::memory::get_meta_slot).
53pub struct BuddyFrameAllocator;
54
55// SAFETY: `BuddyFrameAllocator::allocate_frame` returns 4KiB-aligned,
56// exclusively-owned physical frames.  Exclusive ownership is guaranteed by
57// the buddy's own bitmap + free-list discipline.  Frames allocated with
58// `FramePurpose::PageTable` are always fully zeroed before being returned.
59#[cfg(target_arch = "x86_64")]
60unsafe impl X86FrameAllocator<Size4KiB> for BuddyFrameAllocator {
61    fn allocate_frame(&mut self) -> Option<X86PhysFrame<Size4KiB>> {
62        // SAFETY: `BuddyFrameAllocator` is only ever called from within
63        // `OffsetPageTable` during page-table operations.  Those occur either
64        // during single-threaded early boot, or while the caller holds a lock
65        // that disables IRQs (e.g. the scheduler SpinLock, the AddressSpace
66        // lock).  IRQs are therefore guaranteed to be disabled.
67        let token = unsafe { crate::sync::IrqDisabledToken::token_from_trusted_context() };
68
69        // `PageTable` purpose enforces:
70        //  - `zeroed = true` unconditionally (cannot be overridden by callers).
71        //  - `FrameMeta::flags` stamped with `KERNEL | ALLOCATED`.
72        //  - `FrameMeta::refcount` set to 1 with `Release` ordering after
73        //    zeroing, so any `Acquire` load of the refcount observes a clean
74        //    frame.
75        let frame = FrameAllocOptions::new()
76            .purpose(FramePurpose::PageTable)
77            .allocate(&token)
78            .ok()?;
79
80        X86PhysFrame::from_start_address(frame.start_address).ok()
81    }
82}
83#[cfg(not(target_arch = "x86_64"))]
84impl crate::arch::x86_64::structures::paging::FrameAllocator<crate::arch::xshim::Size4KiB>
85    for BuddyFrameAllocator
86{
87    fn allocate_frame(
88        &mut self,
89    ) -> Option<crate::arch::xshim::PhysFrame<crate::arch::xshim::Size4KiB>> {
90        None // R2: real Sv48 frame allocation
91    }
92}
93
94/// Paging initialization flag.
95static mut PAGING_READY: bool = false;
96
97/// Physical address of the kernel's level-4 page table (set at init, never changes).
98static mut KERNEL_CR3: PhysAddr = PhysAddr::new_truncate(0);
99
100/// Serializes mutations of the canonical kernel page tables.
101///
102/// The active-CR3 mapping helpers are still caller-synchronized by their own
103/// higher-level address-space locks, but kernel-global mappings such as vmalloc
104/// must not race while allocating or wiring intermediate page-table levels.
105static KERNEL_PT_LOCK: SpinLock<()> = SpinLock::new(());
106
107/// Returns whether initialized.
108pub fn is_initialized() -> bool {
109    unsafe { *(&raw const PAGING_READY) }
110}
111
112/// Initialize the paging subsystem.
113///
114/// Reads the active CR3 (level-4 page table) and creates an `OffsetPageTable`
115/// mapper using the HHDM offset for physical-to-virtual translation.
116///
117/// Must be called after the buddy allocator and HHDM offset are initialized.
118pub fn init(hhdm_offset: u64) {
119    let phys_offset = VirtAddr::new(hhdm_offset);
120    let (level_4_frame, _flags) = Cr3::read();
121    let level_4_phys = level_4_frame.start_address().as_u64();
122    let level_4_virt = phys_offset + level_4_phys;
123
124    // SAFETY: called once during single-threaded init. The HHDM offset correctly
125    // maps all physical RAM to virtual addresses. CR3 points to a valid page table
126    // set up by the bootloader.
127    unsafe {
128        let kcr3 = &raw mut KERNEL_CR3;
129        *kcr3 = level_4_frame.start_address();
130        let ready = &raw mut PAGING_READY;
131        *ready = true;
132    }
133
134    log::info!(
135        "Paging initialized: CR3={:#x}, HHDM={:#x}, L4 table @ {:#x}",
136        level_4_phys,
137        hhdm_offset,
138        level_4_virt.as_u64(),
139    );
140}
141
142/// Map all RAM regions from the memory map into the HHDM.
143///
144/// This ensures that every byte of physical RAM is accessible through the
145/// higher-half direct map. Should be called after paging::init.
146/// Fix for VMWare Workstation which doesn't identity-map all RAM by default, causing
147/// the kernel to crash when it tries to access unmapped RAM (e.g. for the buddy allocator's
148/// metadata array). The bootloader's initial map only covers the first 1GB of RAM, which is not enough
149/// for our 2GB test VM. This function lazily maps any missing RAM regions on
150/// demand using `ensure_identity_map_range()`, which checks if the region is already mapped
151/// before mapping it. This allows the kernel to boot successfully on VMWare Workstation without
152/// requiring changes to the bootloader configuration.
153///
154pub fn map_all_ram(memory_regions: &[crate::boot::entry::MemoryRegion]) {
155    use crate::boot::entry::MemoryKind;
156
157    for region in memory_regions {
158        if matches!(region.kind, MemoryKind::Free | MemoryKind::Reclaim) {
159            log::debug!(
160                "Mapping RAM region to HHDM: phys=0x{:x}..0x{:x}",
161                region.base,
162                region.base + region.size
163            );
164            ensure_hhdm_range(
165                region.base,
166                region.size,
167                PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE,
168            );
169        }
170    }
171}
172
173/// Map a virtual page to a physical frame with the given flags.
174///
175/// Intermediate page tables are allocated from the buddy allocator as needed.
176pub fn map_page(
177    page: Page<Size4KiB>,
178    frame: X86PhysFrame<Size4KiB>,
179    flags: PageTableFlags,
180) -> Result<(), &'static str> {
181    if !is_initialized() {
182        return Err("Paging not initialized");
183    }
184    let phys_offset = VirtAddr::new(crate::memory::hhdm_offset());
185    let (level_4_frame, _) = Cr3::read();
186    let level_4_virt = phys_offset + level_4_frame.start_address().as_u64();
187    // SAFETY: level_4_virt points to the active CR3 PML4 via HHDM.
188    let mapper = unsafe { &mut *level_4_virt.as_mut_ptr::<PageTable>() };
189    let mut mapper = unsafe { OffsetPageTable::new(mapper, phys_offset) };
190    let mut allocator = BuddyFrameAllocator;
191
192    unsafe {
193        mapper
194            .map_to(page, frame, flags, &mut allocator)
195            .map_err(|_| "Failed to map page")?
196            .flush();
197    }
198    Ok(())
199}
200
201/// Map a page into the kernel's canonical page tables (not the active CR3).
202///
203/// This ensures that the mapping is visible from all address spaces, because
204/// every user address space clones the kernel half (PML4[256..512]) from the
205/// kernel's L4 table at creation time.
206///
207/// Used by vmalloc so that heap allocations are kernel-global.
208/// Intermediate page tables are allocated from the buddy allocator as needed.
209pub fn map_page_kernel(
210    page: Page<Size4KiB>,
211    frame: X86PhysFrame<Size4KiB>,
212    flags: PageTableFlags,
213) -> Result<(), &'static str> {
214    if !is_initialized() {
215        return Err("Paging not initialized");
216    }
217    let _guard = KERNEL_PT_LOCK.lock();
218    // SAFETY: KERNEL_CR3 is set once during init and never changes.
219    let kernel_cr3 = unsafe { *(&raw const KERNEL_CR3) };
220    let phys_offset = VirtAddr::new(crate::memory::hhdm_offset());
221    let level_4_virt = phys_offset + kernel_cr3.as_u64();
222    // SAFETY: level_4_virt points to the kernel's L4 table via HHDM.
223    let mapper = unsafe { &mut *level_4_virt.as_mut_ptr::<PageTable>() };
224    let mut mapper = unsafe { OffsetPageTable::new(mapper, phys_offset) };
225    let mut allocator = BuddyFrameAllocator;
226
227    unsafe {
228        mapper
229            .map_to(page, frame, flags, &mut allocator)
230            .map_err(|_| "Failed to map page (kernel)")?
231            .flush();
232    }
233    Ok(())
234}
235
236/// Unmap a page from the active CR3, returning the physical frame.
237pub fn unmap_page(page: Page<Size4KiB>) -> Result<X86PhysFrame<Size4KiB>, &'static str> {
238    if !is_initialized() {
239        return Err("Paging not initialized");
240    }
241    let phys_offset = VirtAddr::new(crate::memory::hhdm_offset());
242    let (level_4_frame, _) = Cr3::read();
243    let level_4_virt = phys_offset + level_4_frame.start_address().as_u64();
244    // SAFETY: level_4_virt points to the active CR3 PML4 via HHDM.
245    let mapper = unsafe { &mut *level_4_virt.as_mut_ptr::<PageTable>() };
246    let mut mapper = unsafe { OffsetPageTable::new(mapper, phys_offset) };
247    let (frame, flush) = mapper.unmap(page).map_err(|_| "Failed to unmap page")?;
248    flush.flush();
249    Ok(frame)
250}
251
252/// Unmap a page from the kernel's canonical page tables.
253///
254/// This is the counterpart to `map_page_kernel`. It removes the mapping from
255/// the kernel's L4 table so that the page is no longer visible in any address
256/// space.
257pub fn unmap_page_kernel(page: Page<Size4KiB>) -> Result<X86PhysFrame<Size4KiB>, &'static str> {
258    if !is_initialized() {
259        return Err("Paging not initialized");
260    }
261    let _guard = KERNEL_PT_LOCK.lock();
262    // SAFETY: KERNEL_CR3 is set once during init and never changes.
263    let kernel_cr3 = unsafe { *(&raw const KERNEL_CR3) };
264    let phys_offset = VirtAddr::new(crate::memory::hhdm_offset());
265    let level_4_virt = phys_offset + kernel_cr3.as_u64();
266    // SAFETY: level_4_virt points to the kernel's L4 table via HHDM.
267    let mapper = unsafe { &mut *level_4_virt.as_mut_ptr::<PageTable>() };
268    let mut mapper = unsafe { OffsetPageTable::new(mapper, phys_offset) };
269    let (frame, flush) = mapper
270        .unmap(page)
271        .map_err(|_| "Failed to unmap page (kernel)")?;
272    flush.flush();
273    Ok(frame)
274}
275
276/// Translate a virtual address to its mapped physical address.
277///
278/// Returns `None` if the address is not mapped.
279pub fn translate(addr: VirtAddr) -> Option<PhysAddr> {
280    if !is_initialized() {
281        return None;
282    }
283    let phys_offset = VirtAddr::new(crate::memory::hhdm_offset());
284    let (level_4_frame, _) = Cr3::read();
285    let level_4_virt = phys_offset + level_4_frame.start_address().as_u64();
286    // SAFETY: level_4_virt points to the active CR3 PML4 via HHDM.
287    let mapper = unsafe { &mut *level_4_virt.as_mut_ptr::<PageTable>() };
288    let mapper = unsafe { OffsetPageTable::new(mapper, phys_offset) };
289    mapper.translate_addr(addr)
290}
291
292fn translate_via_active_page_tables(addr: VirtAddr) -> Option<PhysAddr> {
293    let hhdm = crate::memory::hhdm_offset();
294    let (level_4_frame, _) = Cr3::read();
295
296    unsafe {
297        let l4_ptr = (level_4_frame.start_address().as_u64() + hhdm) as *const u64;
298        let l4e = *l4_ptr.add(((addr.as_u64() >> 39) & 0x1FF) as usize);
299        if l4e & 1 == 0 {
300            return None;
301        }
302
303        let l3_ptr = ((l4e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
304        let l3e = *l3_ptr.add(((addr.as_u64() >> 30) & 0x1FF) as usize);
305        if l3e & 1 == 0 {
306            return None;
307        }
308        if l3e & 0x80 != 0 {
309            return Some(PhysAddr::new(
310                (l3e & 0x000F_FFFF_C000_0000) + (addr.as_u64() & 0x3FFF_FFFF),
311            ));
312        }
313
314        let l2_ptr = ((l3e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
315        let l2e = *l2_ptr.add(((addr.as_u64() >> 21) & 0x1FF) as usize);
316        if l2e & 1 == 0 {
317            return None;
318        }
319        if l2e & 0x80 != 0 {
320            return Some(PhysAddr::new(
321                (l2e & 0x000F_FFFF_FFE0_0000) + (addr.as_u64() & 0x1F_FFFF),
322            ));
323        }
324
325        let l1_ptr = ((l2e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
326        let l1e = *l1_ptr.add(((addr.as_u64() >> 12) & 0x1FF) as usize);
327        if l1e & 1 == 0 {
328            return None;
329        }
330
331        Some(PhysAddr::new(
332            (l1e & 0x000F_FFFF_FFFF_F000) + (addr.as_u64() & 0xFFF),
333        ))
334    }
335}
336
337/// Returns whether the current page tables map the HHDM view of the whole range.
338///
339/// This helper is safe before `paging::init()` and is intended for early boot
340/// allocators that must only touch memory already reachable through the current
341/// firmware-provided direct map.
342pub fn is_hhdm_range_mapped_now(phys_base: u64, size: u64) -> bool {
343    if size == 0 {
344        return true;
345    }
346
347    let start = phys_base & !0xFFF;
348    let end = phys_base.saturating_add(size).saturating_add(0xFFF) & !0xFFF;
349
350    crate::e9_mark!(b'*');
351    let mut phys = start;
352    while phys < end {
353        let virt = VirtAddr::new(crate::memory::phys_to_virt(phys));
354        let mapped = match translate_via_active_page_tables(virt) {
355            Some(m) => m,
356            None => return false,
357        };
358        if mapped.as_u64() != phys {
359            return false;
360        }
361        phys = phys.saturating_add(4096);
362    }
363    crate::e9_mark!(b'+');
364    true
365}
366
367/// Read the current CR3 value (physical address of the active level-4 page table).
368pub fn active_page_table() -> PhysAddr {
369    let (frame, _) = Cr3::read();
370    frame.start_address()
371}
372
373/// Return the physical address of the kernel's level-4 page table.
374///
375/// This is the CR3 value captured at init time : used by `AddressSpace::new_user()`
376/// to clone kernel mappings (PML4 entries 256..512) into new address spaces.
377pub fn kernel_l4_phys() -> PhysAddr {
378    // SAFETY: Written once during single-threaded init, read-only after that.
379    unsafe { *(&raw const KERNEL_CR3) }
380}
381
382/// Ensure a physical address is identity-mapped in the HHDM region.
383///
384/// If the page is not present, it is mapped with Read/Write permissions.
385/// This is used to lazily map MMIO or legacy BIOS regions (like ACPI tables)
386/// that might have been skipped by the bootloader's initial map.
387pub fn ensure_identity_map(phys_addr: u64) {
388    let virt_addr = crate::memory::phys_to_virt(phys_addr);
389    let page = Page::<Size4KiB>::containing_address(VirtAddr::new(virt_addr));
390    let frame = X86PhysFrame::<Size4KiB>::containing_address(PhysAddr::new(phys_addr));
391
392    if translate(VirtAddr::new(virt_addr)).is_none() {
393        log::debug!(
394            "Identity mapping missing page: {:#x} -> {:#x}",
395            phys_addr,
396            virt_addr
397        );
398        let flags = uncached_hhdm_flags();
399        if let Err(e) = map_page(page, frame, flags) {
400            log::error!("Failed to identity map {:#x}: {}", phys_addr, e);
401        }
402    }
403}
404
405/// Ensure a physical range is mapped in the HHDM region.
406///
407/// Builds a single `OffsetPageTable` for the entire range instead of
408/// one per page, and emits a single summary log instead of per-page noise.
409pub fn ensure_identity_map_range(phys_base: u64, size: u64) {
410    ensure_hhdm_range(phys_base, size, uncached_hhdm_flags());
411}
412
413fn uncached_hhdm_flags() -> PageTableFlags {
414    let flags = PageTableFlags::PRESENT
415        | PageTableFlags::WRITABLE
416        | PageTableFlags::NO_EXECUTE
417        | PageTableFlags::NO_CACHE;
418    #[cfg(target_arch = "x86_64")]
419    let flags = flags | PageTableFlags::WRITE_THROUGH; // PAT[3] = UC, not UC-.
420    flags
421}
422
423// Existing mappings retain their cache policy and ELF protections. Only the
424// RAM-map caller requests WB; missing MMIO/firmware pages use UC and NX.
425fn ensure_hhdm_range(phys_base: u64, size: u64, flags: PageTableFlags) {
426    if size == 0 || !is_initialized() {
427        return;
428    }
429
430    let page_size = 4096u64;
431    let start = phys_base & !(page_size - 1);
432    let end = (phys_base.saturating_add(size).saturating_add(page_size - 1)) & !(page_size - 1);
433    if start >= end {
434        return;
435    }
436
437    let phys_offset = VirtAddr::new(crate::memory::hhdm_offset());
438    let (level_4_frame, _) = Cr3::read();
439    let level_4_virt = phys_offset + level_4_frame.start_address().as_u64();
440
441    // SAFETY: level_4_virt points to the active CR3 PML4 via HHDM.
442    let l4_table = unsafe { &mut *level_4_virt.as_mut_ptr::<PageTable>() };
443    let mut mapper = unsafe { OffsetPageTable::new(l4_table, phys_offset) };
444    let mut allocator = BuddyFrameAllocator;
445
446    let mut mapped_count: u64 = 0;
447    let mut p = start;
448    while p < end {
449        let virt = VirtAddr::new(crate::memory::phys_to_virt(p));
450        // Only map if not already present.
451        if mapper.translate_addr(virt).is_none() {
452            let page = Page::<Size4KiB>::containing_address(virt);
453            let frame = X86PhysFrame::<Size4KiB>::containing_address(PhysAddr::new(p));
454            // SAFETY: frame is a valid physical page; mapper uses HHDM offset.
455            match unsafe { mapper.map_to(page, frame, flags, &mut allocator) } {
456                Ok(flush) => {
457                    flush.flush();
458                    mapped_count += 1;
459                }
460                Err(_) => {
461                    log::error!("ensure_identity_map_range: failed to map {:#x}", p);
462                }
463            }
464        }
465        p = p.saturating_add(page_size);
466    }
467
468    if mapped_count > 0 {
469        log::debug!(
470            "Identity mapped {} pages: phys {:#x}..{:#x}",
471            mapped_count,
472            start,
473            end,
474        );
475    }
476}
477
478/// Revoke the loader's temporary identity RX pages before starting allocators.
479///
480/// # Safety
481/// BSP-only, interrupts disabled, before AP startup. CR3 must be the Strat9
482/// UEFI loader's tables and the HHDM must already be set. The kernel runs in its
483/// separate higher-half mapping. There are no global leaves in these tables.
484#[cfg(target_arch = "x86_64")]
485pub unsafe fn retire_uefi_identity_code() {
486    const ADDRESS: u64 = 0x000F_FFFF_FFFF_F000;
487    const NX: u64 = 1 << 63;
488    unsafe fn retire(table_phys: u64, level: u8) {
489        let table = crate::memory::phys_to_virt(table_phys) as *mut u64;
490        for index in 0..512 {
491            let slot = unsafe { table.add(index) };
492            let entry = unsafe { slot.read_volatile() };
493            if entry & 1 == 0 {
494                continue;
495            }
496            if level == 1 || entry & (1 << 7) != 0 {
497                if entry & NX == 0 {
498                    unsafe { slot.write_volatile(entry | NX | 2) };
499                }
500            } else {
501                unsafe { retire(entry & ADDRESS, level - 1) };
502            }
503        }
504    }
505    let cr3: u64;
506    unsafe {
507        core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nomem, nostack, preserves_flags))
508    };
509    let root = crate::memory::phys_to_virt(cr3 & ADDRESS) as *const u64;
510    let identity = unsafe { root.read_volatile() };
511    if identity & 1 != 0 {
512        unsafe { retire(identity & ADDRESS, 3) };
513    }
514    unsafe { core::arch::asm!("mov cr3, {}", in(reg) cr3, options(nostack, preserves_flags)) };
515}
516
517/// Change execution of just the existing 4 KiB identity trampoline page,
518/// preserving its cache selector. BSP enables it before SIPI and retires it
519/// after every AP has left it. APs invalidate their TLB at the boot barrier.
520#[cfg(target_arch = "x86_64")]
521pub fn set_trampoline_execution(physical: u64, executable: bool) -> Result<(), &'static str> {
522    if physical != 0x8000 {
523        return Err("unexpected AP trampoline page");
524    }
525    let _guard = KERNEL_PT_LOCK.lock();
526    let (frame, _) = Cr3::read();
527    let mut table = frame.start_address().as_u64();
528    const ADDRESS: u64 = 0x000F_FFFF_FFFF_F000;
529    for shift in [39, 30, 21, 12] {
530        let slot = (crate::memory::phys_to_virt(table) as *mut u64)
531            .wrapping_add(((physical >> shift) & 511) as usize);
532        let entry = unsafe { slot.read_volatile() };
533        if entry & 1 == 0 {
534            return Err("missing AP identity page");
535        }
536        if shift != 12 && entry & (1 << 7) != 0 {
537            // Legacy loaders may supply an already executable huge identity
538            // mapping. Do not silently make a whole NX huge page executable.
539            return if executable && entry & (1 << 63) == 0 {
540                Ok(())
541            } else {
542                Err("AP identity mapping requires a 4 KiB leaf")
543            };
544        }
545        if shift == 12 {
546            if entry & ADDRESS != physical {
547                return Err("AP identity mapping collision");
548            }
549            unsafe {
550                let updated = if executable {
551                    entry & !((1 << 63) | 2)
552                } else {
553                    entry | (1 << 63)
554                }; // Retired trampoline stays read-only.
555                slot.write_volatile(updated);
556                core::arch::asm!("invlpg [{}]", in(reg) physical, options(nostack, preserves_flags));
557            }
558            return Ok(());
559        }
560        table = entry & ADDRESS;
561    }
562    Err("invalid AP identity mapping")
563}