1use crate::vmem::{
16 BasicMapping, CowMapping, MapRequest, MapResponse, Mapping, MappingKind, TableMovabilityBase,
17 TableOps, TableReadOps, UpdateParent, UpdateParentNone, UpdateParentTable, Void, modify_ptes,
18 write_entry_updating,
19};
20
21#[derive(Copy, Clone)]
22pub(in crate::vmem) struct UpdateParentRoot {}
23
24#[inline(always)]
32#[allow(clippy::useless_conversion)]
33pub(super) unsafe fn read_pte_if_present<Op: TableReadOps>(
34 op: &Op,
35 entry_ptr: Op::TableAddr,
36) -> Option<u64> {
37 let pte: u64 = unsafe { op.read_entry(entry_ptr) }.into();
38 if (pte & PAGE_PRESENT) != 0 {
39 Some(pte)
40 } else {
41 None
42 }
43}
44
45pub(super) unsafe fn require_pte_exist<Op: TableReadOps, P: UpdateParent<Op>>(
50 op: &Op,
51 x: MapResponse<Op, P>,
52) -> Option<MapRequest<Op, P::ChildType>>
53where
54 P::ChildType: UpdateParent<Op>,
55{
56 unsafe { read_pte_if_present(op, x.entry_ptr) }.map(|pte| MapRequest {
57 #[allow(clippy::unnecessary_cast)]
58 table_base: Op::from_phys((pte & PTE_ADDR_MASK) as PhysAddr),
59 vmin: x.vmin,
60 len: x.len,
61 update_parent: x.update_parent.for_child_at_entry(x.entry_ptr),
62 })
63}
64
65pub const PAGE_PRESENT: u64 = 1;
86const PAGE_RW: u64 = 1 << 1;
88const PAGE_NX: u64 = 1 << 63;
90pub const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
93const PAGE_USER_ACCESS_DISABLED: u64 = 0 << 2; const PAGE_DIRTY_SET: u64 = 1 << 6; const PAGE_ACCESSED_SET: u64 = 1 << 5; const PAGE_CACHE_ENABLED: u64 = 0 << 4; const PAGE_WRITE_BACK: u64 = 0 << 3; const PAGE_PAT_WB: u64 = 0 << 7; const PTE_AVL_MASK: u64 = 0x0000_0000_0000_0E00;
103const PAGE_AVL_COW: u64 = 1 << 9;
104
105#[inline(always)]
107const fn page_rw_flag(writable: bool) -> u64 {
108 if writable { PAGE_RW } else { 0 }
109}
110
111#[inline(always)]
113const fn page_nx_flag(executable: bool) -> u64 {
114 if executable { 0 } else { PAGE_NX }
115}
116
117#[allow(clippy::identity_op)]
119#[allow(clippy::precedence)]
120fn pte_for_table<Op: TableOps>(table_addr: Op::TableAddr) -> u64 {
121 Op::to_phys(table_addr) |
122 PAGE_ACCESSED_SET | PAGE_CACHE_ENABLED | PAGE_WRITE_BACK | PAGE_USER_ACCESS_DISABLED |PAGE_RW | PAGE_PRESENT }
129
130pub(in crate::vmem) trait TableMovability<Op: TableReadOps + ?Sized, TableMoveInfo> {
134 type RootUpdateParent: UpdateParent<Op, TableMoveInfo = TableMoveInfo>;
135 fn root_update_parent() -> Self::RootUpdateParent;
136}
137impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> TableMovability<Op, Op::TableAddr>
138 for crate::vmem::MayMoveTable
139{
140 type RootUpdateParent = UpdateParentRoot;
141 fn root_update_parent() -> Self::RootUpdateParent {
142 UpdateParentRoot {}
143 }
144}
145impl<Op: TableReadOps> TableMovability<Op, Void> for crate::vmem::MayNotMoveTable {
146 type RootUpdateParent = UpdateParentNone;
147 fn root_update_parent() -> Self::RootUpdateParent {
148 UpdateParentNone {}
149 }
150}
151
152impl<
153 Op: TableOps<TableMovability = crate::vmem::MayMoveTable>,
154 P: UpdateParent<Op, TableMoveInfo = Op::TableAddr>,
155> UpdateParent<Op> for UpdateParentTable<Op, P>
156{
157 type TableMoveInfo = Op::TableAddr;
158 type ChildType = UpdateParentTable<Op, Self>;
159 fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
160 let pte = pte_for_table::<Op>(new_ptr);
161 unsafe {
162 write_entry_updating(op, self.parent, self.entry_ptr, pte);
163 }
164 }
165 fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
166 Self::ChildType::new(self, entry_ptr)
167 }
168}
169
170impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> UpdateParent<Op>
171 for UpdateParentRoot
172{
173 type TableMoveInfo = Op::TableAddr;
174 type ChildType = UpdateParentTable<Op, Self>;
175 fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
176 unsafe {
177 op.update_root(new_ptr);
178 }
179 }
180 fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
181 Self::ChildType::new(self, entry_ptr)
182 }
183}
184
185unsafe fn alloc_pte_if_needed<
190 Op: TableOps,
191 P: UpdateParent<
192 Op,
193 TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
194 >,
195>(
196 op: &Op,
197 x: MapResponse<Op, P>,
198) -> MapRequest<Op, P::ChildType>
199where
200 P::ChildType: UpdateParent<Op>,
201{
202 let new_update_parent = x.update_parent.for_child_at_entry(x.entry_ptr);
203 if let Some(pte) = unsafe { read_pte_if_present(op, x.entry_ptr) } {
204 return MapRequest {
205 table_base: Op::from_phys(pte & PTE_ADDR_MASK),
206 vmin: x.vmin,
207 len: x.len,
208 update_parent: new_update_parent,
209 };
210 }
211
212 let page_addr = unsafe { op.alloc_table() };
213
214 let pte = pte_for_table::<Op>(page_addr);
215 unsafe {
216 write_entry_updating(op, x.update_parent, x.entry_ptr, pte);
217 };
218 MapRequest {
219 table_base: page_addr,
220 vmin: x.vmin,
221 len: x.len,
222 update_parent: new_update_parent,
223 }
224}
225
226#[allow(clippy::identity_op)]
231#[allow(clippy::precedence)]
232unsafe fn map_page<
233 Op: TableOps,
234 P: UpdateParent<
235 Op,
236 TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
237 >,
238>(
239 op: &Op,
240 mapping: &Mapping,
241 r: MapResponse<Op, P>,
242) {
243 let pte = match &mapping.kind {
244 MappingKind::Basic(bm) =>
245 {
253 (mapping.phys_base + (r.vmin - mapping.virt_base)) |
254 page_nx_flag(bm.executable) | PAGE_PAT_WB | PAGE_DIRTY_SET | PAGE_ACCESSED_SET | PAGE_CACHE_ENABLED | PAGE_WRITE_BACK | PAGE_USER_ACCESS_DISABLED | page_rw_flag(bm.writable) | PAGE_PRESENT }
264 MappingKind::Cow(cm) => {
265 (mapping.phys_base + (r.vmin - mapping.virt_base)) |
266 page_nx_flag(cm.executable) | PAGE_AVL_COW |
268 PAGE_PAT_WB | PAGE_DIRTY_SET | PAGE_ACCESSED_SET | PAGE_CACHE_ENABLED | PAGE_WRITE_BACK | PAGE_USER_ACCESS_DISABLED | 0 | PAGE_PRESENT }
277 MappingKind::Unmapped => 0,
278 };
279 unsafe {
280 write_entry_updating(op, r.update_parent, r.entry_ptr, pte);
281 }
282}
283
284#[allow(clippy::missing_safety_doc)]
305pub unsafe fn walk_va_spaces<Op: TableReadOps>(
306 op: &Op,
307 roots: &[Op::TableAddr],
308 address: u64,
309 len: u64,
310) -> ::alloc::vec::Vec<(
311 crate::vmem::SpaceId,
312 ::alloc::vec::Vec<crate::vmem::SpaceAwareMapping>,
313)> {
314 use ::alloc::vec::Vec;
315
316 let mut out: Vec<(crate::vmem::SpaceId, Vec<crate::vmem::SpaceAwareMapping>)> =
317 Vec::with_capacity(roots.len());
318
319 let addr = address & ((1u64 << VA_BITS) - 1);
320 let vmin = addr & !(PAGE_SIZE as u64 - 1);
321 let vmax = core::cmp::min(addr + len, 1u64 << VA_BITS);
322
323 for &root in roots {
324 #[allow(clippy::unnecessary_cast)]
325 let root_id: crate::vmem::SpaceId = Op::to_phys(root) as u64;
326 let mut mappings: Vec<crate::vmem::SpaceAwareMapping> = Vec::new();
327
328 let iter = modify_ptes::<47, 39, Op, _>(MapRequest {
329 table_base: root,
330 vmin,
331 len: vmax.saturating_sub(vmin),
332 update_parent: UpdateParentNone {},
333 })
334 .filter_map(|r| unsafe { require_pte_exist(op, r) })
335 .flat_map(modify_ptes::<38, 30, Op, _>)
336 .filter_map(|r| unsafe { require_pte_exist(op, r) })
337 .flat_map(modify_ptes::<29, 21, Op, _>)
338 .filter_map(|r| unsafe { require_pte_exist(op, r) })
339 .flat_map(modify_ptes::<20, 12, Op, _>);
340
341 for r in iter {
342 let Some(pte) = (unsafe { read_pte_if_present(op, r.entry_ptr) }) else {
343 continue;
344 };
345 let phys_addr = pte & PTE_ADDR_MASK;
346 let sgn_bit = r.vmin >> (VA_BITS - 1);
347 let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
348 let virt_addr = sgn_bits | r.vmin;
349
350 let executable = (pte & PAGE_NX) == 0;
351 let avl = pte & PTE_AVL_MASK;
352 let kind = if avl == PAGE_AVL_COW {
353 MappingKind::Cow(CowMapping {
354 readable: true,
355 executable,
356 })
357 } else {
358 MappingKind::Basic(BasicMapping {
359 readable: true,
360 writable: (pte & PAGE_RW) != 0,
361 executable,
362 })
363 };
364 mappings.push(crate::vmem::SpaceAwareMapping::ThisSpace(Mapping {
365 phys_base: phys_addr,
366 virt_base: virt_addr,
367 len: PAGE_SIZE as u64,
368 kind,
369 }));
370 }
371
372 out.push((root_id, mappings));
373 }
374
375 out
376}
377
378#[allow(clippy::missing_safety_doc)]
382pub unsafe fn space_aware_map<Op: TableOps>(
383 _op: &Op,
384 _ref_map: crate::vmem::SpaceReferenceMapping,
385 _built_roots: &::alloc::collections::BTreeMap<crate::vmem::SpaceId, Op::TableAddr>,
386) {
387}
388
389#[allow(clippy::missing_safety_doc)]
390pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
391 modify_ptes::<47, 39, Op, _>(MapRequest {
392 table_base: op.root_table(),
393 vmin: mapping.virt_base,
394 len: mapping.len,
395 update_parent: Op::TableMovability::root_update_parent(),
396 })
397 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
398 .flat_map(modify_ptes::<38, 30, Op, _>)
399 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
400 .flat_map(modify_ptes::<29, 21, Op, _>)
401 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
402 .flat_map(modify_ptes::<20, 12, Op, _>)
403 .map(|r| unsafe { map_page(op, &mapping, r) })
404 .for_each(drop);
405}
406
407#[allow(clippy::missing_safety_doc)]
425pub unsafe fn virt_to_phys<'a, Op: TableReadOps + 'a>(
426 op: impl core::convert::AsRef<Op> + Copy + 'a,
427 address: u64,
428 len: u64,
429) -> impl Iterator<Item = Mapping> + 'a {
430 let addr = address & ((1u64 << VA_BITS) - 1);
432 let vmin = addr & !(PAGE_SIZE as u64 - 1);
434 let vmax = core::cmp::min(addr + len, 1u64 << VA_BITS);
437 modify_ptes::<47, 39, Op, _>(MapRequest {
438 table_base: op.as_ref().root_table(),
439 vmin,
440 len: vmax - vmin,
441 update_parent: UpdateParentNone {},
442 })
443 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
444 .flat_map(modify_ptes::<38, 30, Op, _>)
445 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
446 .flat_map(modify_ptes::<29, 21, Op, _>)
447 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
448 .flat_map(modify_ptes::<20, 12, Op, _>)
449 .filter_map(move |r| {
450 let pte = unsafe { read_pte_if_present(op.as_ref(), r.entry_ptr) }?;
451 let phys_addr = pte & PTE_ADDR_MASK;
452 let sgn_bit = r.vmin >> (VA_BITS - 1);
454 let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
455 let virt_addr = sgn_bits | r.vmin;
456
457 let executable = (pte & PAGE_NX) == 0;
458 let avl = pte & PTE_AVL_MASK;
459 let kind = if avl == PAGE_AVL_COW {
460 MappingKind::Cow(CowMapping {
461 readable: true,
462 executable,
463 })
464 } else {
465 MappingKind::Basic(BasicMapping {
466 readable: true,
467 writable: (pte & PAGE_RW) != 0,
468 executable,
469 })
470 };
471 Some(Mapping {
472 phys_base: phys_addr,
473 virt_base: virt_addr,
474 len: PAGE_SIZE as u64,
475 kind,
476 })
477 })
478}
479
480const VA_BITS: usize = 48; pub const PAGE_SIZE: usize = 4096;
483pub const PAGE_TABLE_SIZE: usize = 4096;
484pub type PageTableEntry = u64;
485pub type VirtAddr = u64;
486pub type PhysAddr = u64;
487
488#[cfg(test)]
489mod tests {
490 use alloc::vec;
491 use alloc::vec::Vec;
492 use core::cell::RefCell;
493
494 use super::*;
495 use crate::vmem::{
496 BasicMapping, Mapping, MappingKind, MayNotMoveTable, PAGE_TABLE_ENTRIES_PER_TABLE,
497 TableOps, TableReadOps, Void, bits,
498 };
499
500 struct MockTableOps {
503 tables: RefCell<Vec<[u64; PAGE_TABLE_ENTRIES_PER_TABLE]>>,
504 }
505
506 impl core::convert::AsRef<MockTableOps> for MockTableOps {
508 fn as_ref(&self) -> &Self {
509 self
510 }
511 }
512
513 impl MockTableOps {
514 fn new() -> Self {
515 Self {
517 tables: RefCell::new(vec![[0u64; PAGE_TABLE_ENTRIES_PER_TABLE]]),
518 }
519 }
520
521 fn table_count(&self) -> usize {
522 self.tables.borrow().len()
523 }
524
525 fn get_entry(&self, table_idx: usize, entry_idx: usize) -> u64 {
526 self.tables.borrow()[table_idx][entry_idx]
527 }
528 }
529
530 impl TableReadOps for MockTableOps {
531 type TableAddr = (usize, usize); fn entry_addr(addr: Self::TableAddr, entry_offset: u64) -> Self::TableAddr {
534 let phys = Self::to_phys(addr) + entry_offset;
536 Self::from_phys(phys)
537 }
538
539 unsafe fn read_entry(&self, addr: Self::TableAddr) -> u64 {
540 self.tables.borrow()[addr.0][addr.1]
541 }
542
543 fn to_phys(addr: Self::TableAddr) -> PhysAddr {
544 (addr.0 as u64 * PAGE_TABLE_SIZE as u64) + (addr.1 as u64 * 8)
546 }
547
548 fn from_phys(addr: PhysAddr) -> Self::TableAddr {
549 let table_idx = (addr / PAGE_TABLE_SIZE as u64) as usize;
550 let entry_idx = ((addr % PAGE_TABLE_SIZE as u64) / 8) as usize;
551 (table_idx, entry_idx)
552 }
553
554 fn root_table(&self) -> Self::TableAddr {
555 (0, 0)
556 }
557 }
558
559 impl TableOps for MockTableOps {
560 type TableMovability = MayNotMoveTable;
561
562 unsafe fn alloc_table(&self) -> Self::TableAddr {
563 let mut tables = self.tables.borrow_mut();
564 let idx = tables.len();
565 tables.push([0u64; PAGE_TABLE_ENTRIES_PER_TABLE]);
566 (idx, 0)
567 }
568
569 unsafe fn write_entry(&self, addr: Self::TableAddr, entry: u64) -> Option<Void> {
570 self.tables.borrow_mut()[addr.0][addr.1] = entry;
571 None
572 }
573
574 unsafe fn update_root(&self, impossible: Void) {
575 match impossible {}
576 }
577 }
578
579 #[test]
582 fn test_bits_extracts_pml4_index() {
583 let addr: u64 = 0x0000_0080_0000_0000;
586 assert_eq!(bits::<47, 39>(addr), 1);
587 }
588
589 #[test]
590 fn test_bits_extracts_pdpt_index() {
591 let addr: u64 = 0x4000_0000;
594 assert_eq!(bits::<38, 30>(addr), 1);
595 }
596
597 #[test]
598 fn test_bits_extracts_pd_index() {
599 let addr: u64 = 0x0000_0000_0020_0000;
602 assert_eq!(bits::<29, 21>(addr), 1);
603 }
604
605 #[test]
606 fn test_bits_extracts_pt_index() {
607 let addr: u64 = 0x0000_0000_0000_1000;
610 assert_eq!(bits::<20, 12>(addr), 1);
611 }
612
613 #[test]
614 fn test_bits_max_index() {
615 let addr: u64 = 0x0000_FF80_0000_0000;
618 assert_eq!(bits::<47, 39>(addr), 511);
619 }
620
621 #[test]
624 fn test_page_rw_flag_writable() {
625 assert_eq!(page_rw_flag(true), PAGE_RW);
626 }
627
628 #[test]
629 fn test_page_rw_flag_readonly() {
630 assert_eq!(page_rw_flag(false), 0);
631 }
632
633 #[test]
634 fn test_page_nx_flag_executable() {
635 assert_eq!(page_nx_flag(true), 0); }
637
638 #[test]
639 fn test_page_nx_flag_not_executable() {
640 assert_eq!(page_nx_flag(false), PAGE_NX);
641 }
642
643 #[test]
646 fn test_map_single_page() {
647 let ops = MockTableOps::new();
648 let mapping = Mapping {
649 phys_base: 0x1000,
650 virt_base: 0x1000,
651 len: PAGE_SIZE as u64,
652 kind: MappingKind::Basic(BasicMapping {
653 readable: true,
654 writable: true,
655 executable: false,
656 }),
657 };
658
659 unsafe { map(&ops, mapping) };
660
661 assert_eq!(ops.table_count(), 4);
663
664 let pml4_entry = ops.get_entry(0, 0);
666 assert_ne!(pml4_entry & PAGE_PRESENT, 0, "PML4 entry should be present");
667 assert_ne!(pml4_entry & PAGE_RW, 0, "PML4 entry should be writable");
668
669 let pte = ops.get_entry(3, 1);
672 assert_ne!(pte & PAGE_PRESENT, 0, "PTE should be present");
673 assert_ne!(pte & PAGE_RW, 0, "PTE should be writable");
674 assert_ne!(pte & PAGE_NX, 0, "PTE should have NX set (not executable)");
675 assert_eq!(pte & PTE_ADDR_MASK, 0x1000, "PTE should map to phys 0x1000");
676 }
677
678 #[test]
679 fn test_map_executable_page() {
680 let ops = MockTableOps::new();
681 let mapping = Mapping {
682 phys_base: 0x2000,
683 virt_base: 0x2000,
684 len: PAGE_SIZE as u64,
685 kind: MappingKind::Basic(BasicMapping {
686 readable: true,
687 writable: false,
688 executable: true,
689 }),
690 };
691
692 unsafe { map(&ops, mapping) };
693
694 let pte = ops.get_entry(3, 2);
696 assert_ne!(pte & PAGE_PRESENT, 0, "PTE should be present");
697 assert_eq!(pte & PAGE_RW, 0, "PTE should be read-only");
698 assert_eq!(pte & PAGE_NX, 0, "PTE should NOT have NX set (executable)");
699 }
700
701 #[test]
702 fn test_map_multiple_pages() {
703 let ops = MockTableOps::new();
704 let mapping = Mapping {
705 phys_base: 0x10000,
706 virt_base: 0x10000,
707 len: 4 * PAGE_SIZE as u64, kind: MappingKind::Basic(BasicMapping {
709 readable: true,
710 writable: true,
711 executable: false,
712 }),
713 };
714
715 unsafe { map(&ops, mapping) };
716
717 for i in 0..4 {
719 let entry_idx = 16 + i; let pte = ops.get_entry(3, entry_idx);
721 assert_ne!(pte & PAGE_PRESENT, 0, "PTE {} should be present", i);
722 let expected_phys = 0x10000 + (i as u64 * PAGE_SIZE as u64);
723 assert_eq!(
724 pte & PTE_ADDR_MASK,
725 expected_phys,
726 "PTE {} should map to correct phys addr",
727 i
728 );
729 }
730 }
731
732 #[test]
733 fn test_map_reuses_existing_tables() {
734 let ops = MockTableOps::new();
735
736 let mapping1 = Mapping {
738 phys_base: 0x1000,
739 virt_base: 0x1000,
740 len: PAGE_SIZE as u64,
741 kind: MappingKind::Basic(BasicMapping {
742 readable: true,
743 writable: true,
744 executable: false,
745 }),
746 };
747 unsafe { map(&ops, mapping1) };
748 let tables_after_first = ops.table_count();
749
750 let mapping2 = Mapping {
752 phys_base: 0x5000,
753 virt_base: 0x5000,
754 len: PAGE_SIZE as u64,
755 kind: MappingKind::Basic(BasicMapping {
756 readable: true,
757 writable: true,
758 executable: false,
759 }),
760 };
761 unsafe { map(&ops, mapping2) };
762
763 assert_eq!(
765 ops.table_count(),
766 tables_after_first,
767 "Should reuse existing page tables"
768 );
769 }
770
771 #[test]
774 fn test_virt_to_phys_mapped_address() {
775 let ops = MockTableOps::new();
776 let mapping = Mapping {
777 phys_base: 0x1000,
778 virt_base: 0x1000,
779 len: PAGE_SIZE as u64,
780 kind: MappingKind::Basic(BasicMapping {
781 readable: true,
782 writable: true,
783 executable: false,
784 }),
785 };
786
787 unsafe { map(&ops, mapping) };
788
789 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
790 assert!(result.is_some(), "Should find mapped address");
791 let mapping = result.unwrap();
792 assert_eq!(mapping.phys_base, 0x1000);
793 }
794
795 #[test]
796 fn test_virt_to_phys_unaligned_virt() {
797 let ops = MockTableOps::new();
798 let mapping = Mapping {
799 phys_base: 0x1000,
800 virt_base: 0x1000,
801 len: PAGE_SIZE as u64,
802 kind: MappingKind::Basic(BasicMapping {
803 readable: true,
804 writable: true,
805 executable: false,
806 }),
807 };
808
809 unsafe { map(&ops, mapping) };
810
811 let result = unsafe { virt_to_phys(&ops, 0x1234, 1).next() };
812 assert!(result.is_some(), "Should find mapped address");
813 let mapping = result.unwrap();
814 assert_eq!(mapping.phys_base, 0x1000);
815 }
816
817 #[test]
818 fn test_virt_to_phys_unaligned_virt_and_across_pages_len() {
819 let ops = MockTableOps::new();
820 let mapping = Mapping {
821 phys_base: 0x1000,
822 virt_base: 0x1000,
823 len: 2 * PAGE_SIZE as u64, kind: MappingKind::Basic(BasicMapping {
825 readable: true,
826 writable: true,
827 executable: false,
828 }),
829 };
830
831 unsafe { map(&ops, mapping) };
832
833 let mappings = unsafe { virt_to_phys(&ops, 0x1F00, 0x300).collect::<Vec<_>>() };
834 assert_eq!(mappings.len(), 2, "Should return 2 mappings for 2 pages");
835 assert_eq!(mappings[0].phys_base, 0x1000);
836 assert_eq!(mappings[1].phys_base, 0x2000);
837 }
838
839 #[test]
840 fn test_virt_to_phys_unaligned_virt_and_multiple_page_len() {
841 let ops = MockTableOps::new();
842 let mapping = Mapping {
843 phys_base: 0x1000,
844 virt_base: 0x1000,
845 len: PAGE_SIZE as u64 * 2 + 0x200, kind: MappingKind::Basic(BasicMapping {
847 readable: true,
848 writable: true,
849 executable: false,
850 }),
851 };
852
853 unsafe { map(&ops, mapping) };
854
855 let mappings =
856 unsafe { virt_to_phys(&ops, 0x1234, PAGE_SIZE as u64 * 2 + 0x10).collect::<Vec<_>>() };
857 assert_eq!(mappings.len(), 3, "Should return 3 mappings for 3 pages");
858 assert_eq!(mappings[0].phys_base, 0x1000);
859 assert_eq!(mappings[1].phys_base, 0x2000);
860 assert_eq!(mappings[2].phys_base, 0x3000);
861 }
862
863 #[test]
864 fn test_virt_to_phys_perms() {
865 let test = |kind| {
866 let ops = MockTableOps::new();
867 let mapping = Mapping {
868 phys_base: 0x1000,
869 virt_base: 0x1000,
870 len: PAGE_SIZE as u64,
871 kind,
872 };
873 unsafe { map(&ops, mapping) };
874 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
875 let mapping = result.unwrap();
876 assert_eq!(mapping.kind, kind);
877 };
878 test(MappingKind::Basic(BasicMapping {
879 readable: true,
880 writable: false,
881 executable: false,
882 }));
883 test(MappingKind::Basic(BasicMapping {
884 readable: true,
885 writable: false,
886 executable: true,
887 }));
888 test(MappingKind::Basic(BasicMapping {
889 readable: true,
890 writable: true,
891 executable: false,
892 }));
893 test(MappingKind::Basic(BasicMapping {
894 readable: true,
895 writable: true,
896 executable: true,
897 }));
898 test(MappingKind::Cow(CowMapping {
899 readable: true,
900 executable: false,
901 }));
902 test(MappingKind::Cow(CowMapping {
903 readable: true,
904 executable: true,
905 }));
906 }
907
908 #[test]
909 fn test_virt_to_phys_unmapped_address() {
910 let ops = MockTableOps::new();
911 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
914 assert!(result.is_none(), "Should return None for unmapped address");
915 }
916
917 #[test]
918 fn test_virt_to_phys_partially_mapped() {
919 let ops = MockTableOps::new();
920 let mapping = Mapping {
921 phys_base: 0x1000,
922 virt_base: 0x1000,
923 len: PAGE_SIZE as u64,
924 kind: MappingKind::Basic(BasicMapping {
925 readable: true,
926 writable: true,
927 executable: false,
928 }),
929 };
930
931 unsafe { map(&ops, mapping) };
932
933 let result = unsafe { virt_to_phys(&ops, 0x5000, 1).next() };
935 assert!(
936 result.is_none(),
937 "Should return None for unmapped address in same PT"
938 );
939 }
940
941 #[test]
944 fn test_modify_pte_iterator_single_page() {
945 let ops = MockTableOps::new();
946 let request = MapRequest {
947 table_base: ops.root_table(),
948 vmin: 0x1000,
949 len: PAGE_SIZE as u64,
950 update_parent: UpdateParentNone {},
951 };
952
953 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
954 assert_eq!(responses.len(), 1, "Single page should yield one response");
955 assert_eq!(responses[0].vmin, 0x1000);
956 assert_eq!(responses[0].len, PAGE_SIZE as u64);
957 }
958
959 #[test]
960 fn test_modify_pte_iterator_multiple_pages() {
961 let ops = MockTableOps::new();
962 let request = MapRequest {
963 table_base: ops.root_table(),
964 vmin: 0x1000,
965 len: 3 * PAGE_SIZE as u64,
966 update_parent: UpdateParentNone {},
967 };
968
969 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
970 assert_eq!(responses.len(), 3, "3 pages should yield 3 responses");
971 }
972
973 #[test]
974 fn test_modify_pte_iterator_zero_length() {
975 let ops = MockTableOps::new();
976 let request = MapRequest {
977 table_base: ops.root_table(),
978 vmin: 0x1000,
979 len: 0,
980 update_parent: UpdateParentNone {},
981 };
982
983 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
984 assert_eq!(responses.len(), 0, "Zero length should yield no responses");
985 }
986
987 #[test]
988 fn test_modify_pte_iterator_unaligned_start() {
989 let ops = MockTableOps::new();
990 let request = MapRequest {
993 table_base: ops.root_table(),
994 vmin: 0x1800,
995 len: 0x1000,
996 update_parent: UpdateParentNone {},
997 };
998
999 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
1000 assert_eq!(
1001 responses.len(),
1002 2,
1003 "Unaligned mapping spanning 2 pages should yield 2 responses"
1004 );
1005 assert_eq!(responses[0].vmin, 0x1800);
1006 assert_eq!(responses[0].len, 0x800); assert_eq!(responses[1].vmin, 0x2000);
1008 assert_eq!(responses[1].len, 0x800); }
1010
1011 #[test]
1014 fn test_entry_addr_from_table_base() {
1015 let result = MockTableOps::entry_addr((2, 0), 40);
1018 assert_eq!(result, (2, 5), "Should return (table 2, entry 5)");
1019 }
1020
1021 #[test]
1022 fn test_entry_addr_with_nonzero_base_entry() {
1023 let result = MockTableOps::entry_addr((1, 10), 16);
1029 assert_eq!(result, (1, 12), "Should add offset to base entry");
1030 }
1031
1032 #[test]
1033 fn test_to_phys_from_phys_roundtrip() {
1034 let addr = (3, 42);
1036 let phys = MockTableOps::to_phys(addr);
1037 let back = MockTableOps::from_phys(phys);
1038 assert_eq!(back, addr, "to_phys/from_phys should roundtrip");
1039 }
1040}