1use crate::vmem::{
29 BasicMapping, CowMapping, MapRequest, MapResponse, Mapping, MappingKind, TableMovabilityBase,
30 TableOps, TableReadOps, UpdateParent, UpdateParentNone, Void, modify_ptes,
31 write_entry_updating,
32};
33
34pub struct UpdateParentTable<Op: TableOps, P: UpdateParent<Op>> {
37 pub(crate) parent: P,
38 pub(crate) entry_ptr: Op::TableAddr,
39}
40impl<Op: TableOps, P: UpdateParent<Op>> Clone for UpdateParentTable<Op, P> {
41 fn clone(&self) -> Self {
42 *self
43 }
44}
45impl<Op: TableOps, P: UpdateParent<Op>> Copy for UpdateParentTable<Op, P> {}
46impl<Op: TableOps, P: UpdateParent<Op>> UpdateParentTable<Op, P> {
47 pub(crate) fn new(parent: P, entry_ptr: Op::TableAddr) -> Self {
48 UpdateParentTable { parent, entry_ptr }
49 }
50}
51
52#[derive(Copy, Clone)]
54pub struct UpdateParentRoot {}
55
56#[inline(always)]
64#[allow(clippy::useless_conversion)]
65pub(super) unsafe fn read_pte_if_present<Op: TableReadOps>(
66 op: &Op,
67 entry_ptr: Op::TableAddr,
68) -> Option<u64> {
69 let pte: u64 = unsafe { op.read_entry(entry_ptr) }.into();
70 if (pte & PAGE_PRESENT) != 0 {
71 Some(pte)
72 } else {
73 None
74 }
75}
76
77pub(super) unsafe fn require_pte_exist<Op: TableReadOps, P: UpdateParent<Op>>(
82 op: &Op,
83 x: MapResponse<Op, P>,
84) -> Option<MapRequest<Op, P::ChildType>>
85where
86 P::ChildType: UpdateParent<Op>,
87{
88 unsafe { read_pte_if_present(op, x.entry_ptr) }.map(|pte| MapRequest {
89 #[allow(clippy::unnecessary_cast)]
90 table_base: Op::from_phys((pte & PTE_ADDR_MASK) as PhysAddr),
91 vmin: x.vmin,
92 len: x.len,
93 update_parent: x.update_parent.for_child_at_entry(x.entry_ptr),
94 })
95}
96
97pub const PAGE_PRESENT: u64 = 1;
118const PAGE_RW: u64 = 1 << 1;
120const PAGE_NX: u64 = 1 << 63;
122pub const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
125const 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;
135const PAGE_AVL_COW: u64 = 1 << 9;
136
137#[inline(always)]
139const fn page_rw_flag(writable: bool) -> u64 {
140 if writable { PAGE_RW } else { 0 }
141}
142
143#[inline(always)]
145const fn page_nx_flag(executable: bool) -> u64 {
146 if executable { 0 } else { PAGE_NX }
147}
148
149#[allow(clippy::identity_op)]
151#[allow(clippy::precedence)]
152fn pte_for_table<Op: TableOps>(table_addr: Op::TableAddr) -> u64 {
153 Op::to_phys(table_addr) |
154 PAGE_ACCESSED_SET | PAGE_CACHE_ENABLED | PAGE_WRITE_BACK | PAGE_USER_ACCESS_DISABLED |PAGE_RW | PAGE_PRESENT }
161
162pub trait TableMovability<Op: TableReadOps + ?Sized, TableMoveInfo> {
166 type RootUpdateParent: UpdateParent<Op, TableMoveInfo = TableMoveInfo>;
167 fn root_update_parent() -> Self::RootUpdateParent;
168}
169impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> TableMovability<Op, Op::TableAddr>
170 for crate::vmem::MayMoveTable
171{
172 type RootUpdateParent = UpdateParentRoot;
173 fn root_update_parent() -> Self::RootUpdateParent {
174 UpdateParentRoot {}
175 }
176}
177impl<Op: TableReadOps> TableMovability<Op, Void> for crate::vmem::MayNotMoveTable {
178 type RootUpdateParent = UpdateParentNone;
179 fn root_update_parent() -> Self::RootUpdateParent {
180 UpdateParentNone {}
181 }
182}
183
184impl<
185 Op: TableOps<TableMovability = crate::vmem::MayMoveTable>,
186 P: UpdateParent<Op, TableMoveInfo = Op::TableAddr>,
187> UpdateParent<Op> for UpdateParentTable<Op, P>
188{
189 type TableMoveInfo = Op::TableAddr;
190 type ChildType = UpdateParentTable<Op, Self>;
191 fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
192 let pte = pte_for_table::<Op>(new_ptr);
193 unsafe {
194 write_entry_updating(op, self.parent, self.entry_ptr, pte);
195 }
196 }
197 fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
198 Self::ChildType::new(self, entry_ptr)
199 }
200}
201
202impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> UpdateParent<Op>
203 for UpdateParentRoot
204{
205 type TableMoveInfo = Op::TableAddr;
206 type ChildType = UpdateParentTable<Op, Self>;
207 fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
208 unsafe {
209 op.update_root(new_ptr);
210 }
211 }
212 fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
213 Self::ChildType::new(self, entry_ptr)
214 }
215}
216
217unsafe fn alloc_pte_if_needed<
222 Op: TableOps,
223 P: UpdateParent<
224 Op,
225 TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
226 >,
227>(
228 op: &Op,
229 x: MapResponse<Op, P>,
230) -> MapRequest<Op, P::ChildType>
231where
232 P::ChildType: UpdateParent<Op>,
233{
234 let new_update_parent = x.update_parent.for_child_at_entry(x.entry_ptr);
235 if let Some(pte) = unsafe { read_pte_if_present(op, x.entry_ptr) } {
236 return MapRequest {
237 table_base: Op::from_phys(pte & PTE_ADDR_MASK),
238 vmin: x.vmin,
239 len: x.len,
240 update_parent: new_update_parent,
241 };
242 }
243
244 let page_addr = unsafe { op.alloc_table() };
245
246 let pte = pte_for_table::<Op>(page_addr);
247 unsafe {
248 write_entry_updating(op, x.update_parent, x.entry_ptr, pte);
249 };
250 MapRequest {
251 table_base: page_addr,
252 vmin: x.vmin,
253 len: x.len,
254 update_parent: new_update_parent,
255 }
256}
257
258#[allow(clippy::identity_op)]
263#[allow(clippy::precedence)]
264unsafe fn map_page<
265 Op: TableOps,
266 P: UpdateParent<
267 Op,
268 TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
269 >,
270>(
271 op: &Op,
272 mapping: &Mapping,
273 r: MapResponse<Op, P>,
274) {
275 let pte = match &mapping.kind {
276 MappingKind::Basic(bm) =>
277 {
285 (mapping.phys_base + (r.vmin - mapping.virt_base)) |
286 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 }
296 MappingKind::Cow(cm) => {
297 (mapping.phys_base + (r.vmin - mapping.virt_base)) |
298 page_nx_flag(cm.executable) | PAGE_AVL_COW |
300 PAGE_PAT_WB | PAGE_DIRTY_SET | PAGE_ACCESSED_SET | PAGE_CACHE_ENABLED | PAGE_WRITE_BACK | PAGE_USER_ACCESS_DISABLED | 0 | PAGE_PRESENT }
309 MappingKind::Unmapped => 0,
310 };
311 unsafe {
312 write_entry_updating(op, r.update_parent, r.entry_ptr, pte);
313 }
314}
315
316#[allow(clippy::missing_safety_doc)]
332pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
333 modify_ptes::<47, 39, Op, _>(MapRequest {
334 table_base: op.root_table(),
335 vmin: mapping.virt_base,
336 len: mapping.len,
337 update_parent: Op::TableMovability::root_update_parent(),
338 })
339 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
340 .flat_map(modify_ptes::<38, 30, Op, _>)
341 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
342 .flat_map(modify_ptes::<29, 21, Op, _>)
343 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
344 .flat_map(modify_ptes::<20, 12, Op, _>)
345 .map(|r| unsafe { map_page(op, &mapping, r) })
346 .for_each(drop);
347}
348
349#[allow(clippy::missing_safety_doc)]
367pub unsafe fn virt_to_phys<'a, Op: TableReadOps + 'a>(
368 op: impl core::convert::AsRef<Op> + Copy + 'a,
369 address: u64,
370 len: u64,
371) -> impl Iterator<Item = Mapping> + 'a {
372 let addr = address & ((1u64 << VA_BITS) - 1);
374 let vmin = addr & !(PAGE_SIZE as u64 - 1);
376 let vmax = core::cmp::min(addr + len, 1u64 << VA_BITS);
379 modify_ptes::<47, 39, Op, _>(MapRequest {
380 table_base: op.as_ref().root_table(),
381 vmin,
382 len: vmax - vmin,
383 update_parent: UpdateParentNone {},
384 })
385 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
386 .flat_map(modify_ptes::<38, 30, Op, _>)
387 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
388 .flat_map(modify_ptes::<29, 21, Op, _>)
389 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
390 .flat_map(modify_ptes::<20, 12, Op, _>)
391 .filter_map(move |r| {
392 let pte = unsafe { read_pte_if_present(op.as_ref(), r.entry_ptr) }?;
393 let phys_addr = pte & PTE_ADDR_MASK;
394 let sgn_bit = r.vmin >> (VA_BITS - 1);
396 let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
397 let virt_addr = sgn_bits | r.vmin;
398
399 let executable = (pte & PAGE_NX) == 0;
400 let avl = pte & PTE_AVL_MASK;
401 let kind = if avl == PAGE_AVL_COW {
402 MappingKind::Cow(CowMapping {
403 readable: true,
404 executable,
405 })
406 } else {
407 MappingKind::Basic(BasicMapping {
408 readable: true,
409 writable: (pte & PAGE_RW) != 0,
410 executable,
411 })
412 };
413 Some(Mapping {
414 phys_base: phys_addr,
415 virt_base: virt_addr,
416 len: PAGE_SIZE as u64,
417 kind,
418 user_accessible: false,
419 })
420 })
421}
422
423const VA_BITS: usize = 48; pub const PAGE_SIZE: usize = 4096;
426pub const PAGE_TABLE_SIZE: usize = 4096;
427pub type PageTableEntry = u64;
428pub type VirtAddr = u64;
429pub type PhysAddr = u64;
430
431#[cfg(test)]
432mod tests {
433 use alloc::vec;
434 use alloc::vec::Vec;
435 use core::cell::RefCell;
436
437 use super::*;
438 use crate::vmem::{
439 BasicMapping, Mapping, MappingKind, MayNotMoveTable, PAGE_TABLE_ENTRIES_PER_TABLE,
440 TableOps, TableReadOps, Void, bits,
441 };
442
443 struct MockTableOps {
446 tables: RefCell<Vec<[u64; PAGE_TABLE_ENTRIES_PER_TABLE]>>,
447 }
448
449 impl core::convert::AsRef<MockTableOps> for MockTableOps {
451 fn as_ref(&self) -> &Self {
452 self
453 }
454 }
455
456 impl MockTableOps {
457 fn new() -> Self {
458 Self {
460 tables: RefCell::new(vec![[0u64; PAGE_TABLE_ENTRIES_PER_TABLE]]),
461 }
462 }
463
464 fn table_count(&self) -> usize {
465 self.tables.borrow().len()
466 }
467
468 fn get_entry(&self, table_idx: usize, entry_idx: usize) -> u64 {
469 self.tables.borrow()[table_idx][entry_idx]
470 }
471 }
472
473 impl TableReadOps for MockTableOps {
474 type TableAddr = (usize, usize); fn entry_addr(addr: Self::TableAddr, entry_offset: u64) -> Self::TableAddr {
477 let phys = Self::to_phys(addr) + entry_offset;
479 Self::from_phys(phys)
480 }
481
482 unsafe fn read_entry(&self, addr: Self::TableAddr) -> u64 {
483 self.tables.borrow()[addr.0][addr.1]
484 }
485
486 fn to_phys(addr: Self::TableAddr) -> PhysAddr {
487 (addr.0 as u64 * PAGE_TABLE_SIZE as u64) + (addr.1 as u64 * 8)
489 }
490
491 fn from_phys(addr: PhysAddr) -> Self::TableAddr {
492 let table_idx = (addr / PAGE_TABLE_SIZE as u64) as usize;
493 let entry_idx = ((addr % PAGE_TABLE_SIZE as u64) / 8) as usize;
494 (table_idx, entry_idx)
495 }
496
497 fn root_table(&self) -> Self::TableAddr {
498 (0, 0)
499 }
500 }
501
502 impl TableOps for MockTableOps {
503 type TableMovability = MayNotMoveTable;
504
505 unsafe fn alloc_table(&self) -> Self::TableAddr {
506 let mut tables = self.tables.borrow_mut();
507 let idx = tables.len();
508 tables.push([0u64; PAGE_TABLE_ENTRIES_PER_TABLE]);
509 (idx, 0)
510 }
511
512 unsafe fn write_entry(&self, addr: Self::TableAddr, entry: u64) -> Option<Void> {
513 self.tables.borrow_mut()[addr.0][addr.1] = entry;
514 None
515 }
516
517 unsafe fn update_root(&self, impossible: Void) {
518 match impossible {}
519 }
520 }
521
522 #[test]
525 fn test_bits_extracts_pml4_index() {
526 let addr: u64 = 0x0000_0080_0000_0000;
529 assert_eq!(bits::<47, 39>(addr), 1);
530 }
531
532 #[test]
533 fn test_bits_extracts_pdpt_index() {
534 let addr: u64 = 0x4000_0000;
537 assert_eq!(bits::<38, 30>(addr), 1);
538 }
539
540 #[test]
541 fn test_bits_extracts_pd_index() {
542 let addr: u64 = 0x0000_0000_0020_0000;
545 assert_eq!(bits::<29, 21>(addr), 1);
546 }
547
548 #[test]
549 fn test_bits_extracts_pt_index() {
550 let addr: u64 = 0x0000_0000_0000_1000;
553 assert_eq!(bits::<20, 12>(addr), 1);
554 }
555
556 #[test]
557 fn test_bits_max_index() {
558 let addr: u64 = 0x0000_FF80_0000_0000;
561 assert_eq!(bits::<47, 39>(addr), 511);
562 }
563
564 #[test]
567 fn test_page_rw_flag_writable() {
568 assert_eq!(page_rw_flag(true), PAGE_RW);
569 }
570
571 #[test]
572 fn test_page_rw_flag_readonly() {
573 assert_eq!(page_rw_flag(false), 0);
574 }
575
576 #[test]
577 fn test_page_nx_flag_executable() {
578 assert_eq!(page_nx_flag(true), 0); }
580
581 #[test]
582 fn test_page_nx_flag_not_executable() {
583 assert_eq!(page_nx_flag(false), PAGE_NX);
584 }
585
586 #[test]
589 fn test_map_single_page() {
590 let ops = MockTableOps::new();
591 let mapping = Mapping {
592 phys_base: 0x1000,
593 virt_base: 0x1000,
594 len: PAGE_SIZE as u64,
595 kind: MappingKind::Basic(BasicMapping {
596 readable: true,
597 writable: true,
598 executable: false,
599 }),
600 user_accessible: false,
601 };
602
603 unsafe { map(&ops, mapping) };
604
605 assert_eq!(ops.table_count(), 4);
607
608 let pml4_entry = ops.get_entry(0, 0);
610 assert_ne!(pml4_entry & PAGE_PRESENT, 0, "PML4 entry should be present");
611 assert_ne!(pml4_entry & PAGE_RW, 0, "PML4 entry should be writable");
612
613 let pte = ops.get_entry(3, 1);
616 assert_ne!(pte & PAGE_PRESENT, 0, "PTE should be present");
617 assert_ne!(pte & PAGE_RW, 0, "PTE should be writable");
618 assert_ne!(pte & PAGE_NX, 0, "PTE should have NX set (not executable)");
619 assert_eq!(pte & PTE_ADDR_MASK, 0x1000, "PTE should map to phys 0x1000");
620 }
621
622 #[test]
623 fn test_map_executable_page() {
624 let ops = MockTableOps::new();
625 let mapping = Mapping {
626 phys_base: 0x2000,
627 virt_base: 0x2000,
628 len: PAGE_SIZE as u64,
629 kind: MappingKind::Basic(BasicMapping {
630 readable: true,
631 writable: false,
632 executable: true,
633 }),
634 user_accessible: false,
635 };
636
637 unsafe { map(&ops, mapping) };
638
639 let pte = ops.get_entry(3, 2);
641 assert_ne!(pte & PAGE_PRESENT, 0, "PTE should be present");
642 assert_eq!(pte & PAGE_RW, 0, "PTE should be read-only");
643 assert_eq!(pte & PAGE_NX, 0, "PTE should NOT have NX set (executable)");
644 }
645
646 #[test]
647 fn test_map_multiple_pages() {
648 let ops = MockTableOps::new();
649 let mapping = Mapping {
650 phys_base: 0x10000,
651 virt_base: 0x10000,
652 len: 4 * PAGE_SIZE as u64, kind: MappingKind::Basic(BasicMapping {
654 readable: true,
655 writable: true,
656 executable: false,
657 }),
658 user_accessible: false,
659 };
660
661 unsafe { map(&ops, mapping) };
662
663 for i in 0..4 {
665 let entry_idx = 16 + i; let pte = ops.get_entry(3, entry_idx);
667 assert_ne!(pte & PAGE_PRESENT, 0, "PTE {} should be present", i);
668 let expected_phys = 0x10000 + (i as u64 * PAGE_SIZE as u64);
669 assert_eq!(
670 pte & PTE_ADDR_MASK,
671 expected_phys,
672 "PTE {} should map to correct phys addr",
673 i
674 );
675 }
676 }
677
678 #[test]
679 fn test_map_reuses_existing_tables() {
680 let ops = MockTableOps::new();
681
682 let mapping1 = Mapping {
684 phys_base: 0x1000,
685 virt_base: 0x1000,
686 len: PAGE_SIZE as u64,
687 kind: MappingKind::Basic(BasicMapping {
688 readable: true,
689 writable: true,
690 executable: false,
691 }),
692 user_accessible: false,
693 };
694 unsafe { map(&ops, mapping1) };
695 let tables_after_first = ops.table_count();
696
697 let mapping2 = Mapping {
699 phys_base: 0x5000,
700 virt_base: 0x5000,
701 len: PAGE_SIZE as u64,
702 kind: MappingKind::Basic(BasicMapping {
703 readable: true,
704 writable: true,
705 executable: false,
706 }),
707 user_accessible: false,
708 };
709 unsafe { map(&ops, mapping2) };
710
711 assert_eq!(
713 ops.table_count(),
714 tables_after_first,
715 "Should reuse existing page tables"
716 );
717 }
718
719 #[test]
722 fn test_virt_to_phys_mapped_address() {
723 let ops = MockTableOps::new();
724 let mapping = Mapping {
725 phys_base: 0x1000,
726 virt_base: 0x1000,
727 len: PAGE_SIZE as u64,
728 kind: MappingKind::Basic(BasicMapping {
729 readable: true,
730 writable: true,
731 executable: false,
732 }),
733 user_accessible: false,
734 };
735
736 unsafe { map(&ops, mapping) };
737
738 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
739 assert!(result.is_some(), "Should find mapped address");
740 let mapping = result.unwrap();
741 assert_eq!(mapping.phys_base, 0x1000);
742 }
743
744 #[test]
745 fn test_virt_to_phys_unaligned_virt() {
746 let ops = MockTableOps::new();
747 let mapping = Mapping {
748 phys_base: 0x1000,
749 virt_base: 0x1000,
750 len: PAGE_SIZE as u64,
751 kind: MappingKind::Basic(BasicMapping {
752 readable: true,
753 writable: true,
754 executable: false,
755 }),
756 user_accessible: false,
757 };
758
759 unsafe { map(&ops, mapping) };
760
761 let result = unsafe { virt_to_phys(&ops, 0x1234, 1).next() };
762 assert!(result.is_some(), "Should find mapped address");
763 let mapping = result.unwrap();
764 assert_eq!(mapping.phys_base, 0x1000);
765 }
766
767 #[test]
768 fn test_virt_to_phys_unaligned_virt_and_across_pages_len() {
769 let ops = MockTableOps::new();
770 let mapping = Mapping {
771 phys_base: 0x1000,
772 virt_base: 0x1000,
773 len: 2 * PAGE_SIZE as u64, kind: MappingKind::Basic(BasicMapping {
775 readable: true,
776 writable: true,
777 executable: false,
778 }),
779 user_accessible: false,
780 };
781
782 unsafe { map(&ops, mapping) };
783
784 let mappings = unsafe { virt_to_phys(&ops, 0x1F00, 0x300).collect::<Vec<_>>() };
785 assert_eq!(mappings.len(), 2, "Should return 2 mappings for 2 pages");
786 assert_eq!(mappings[0].phys_base, 0x1000);
787 assert_eq!(mappings[1].phys_base, 0x2000);
788 }
789
790 #[test]
791 fn test_virt_to_phys_unaligned_virt_and_multiple_page_len() {
792 let ops = MockTableOps::new();
793 let mapping = Mapping {
794 phys_base: 0x1000,
795 virt_base: 0x1000,
796 len: PAGE_SIZE as u64 * 2 + 0x200, kind: MappingKind::Basic(BasicMapping {
798 readable: true,
799 writable: true,
800 executable: false,
801 }),
802 user_accessible: false,
803 };
804
805 unsafe { map(&ops, mapping) };
806
807 let mappings =
808 unsafe { virt_to_phys(&ops, 0x1234, PAGE_SIZE as u64 * 2 + 0x10).collect::<Vec<_>>() };
809 assert_eq!(mappings.len(), 3, "Should return 3 mappings for 3 pages");
810 assert_eq!(mappings[0].phys_base, 0x1000);
811 assert_eq!(mappings[1].phys_base, 0x2000);
812 assert_eq!(mappings[2].phys_base, 0x3000);
813 }
814
815 #[test]
816 fn test_virt_to_phys_perms() {
817 let test = |kind| {
818 let ops = MockTableOps::new();
819 let mapping = Mapping {
820 phys_base: 0x1000,
821 virt_base: 0x1000,
822 len: PAGE_SIZE as u64,
823 kind,
824 user_accessible: false,
825 };
826 unsafe { map(&ops, mapping) };
827 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
828 let mapping = result.unwrap();
829 assert_eq!(mapping.kind, kind);
830 };
831 test(MappingKind::Basic(BasicMapping {
832 readable: true,
833 writable: false,
834 executable: false,
835 }));
836 test(MappingKind::Basic(BasicMapping {
837 readable: true,
838 writable: false,
839 executable: true,
840 }));
841 test(MappingKind::Basic(BasicMapping {
842 readable: true,
843 writable: true,
844 executable: false,
845 }));
846 test(MappingKind::Basic(BasicMapping {
847 readable: true,
848 writable: true,
849 executable: true,
850 }));
851 test(MappingKind::Cow(CowMapping {
852 readable: true,
853 executable: false,
854 }));
855 test(MappingKind::Cow(CowMapping {
856 readable: true,
857 executable: true,
858 }));
859 }
860
861 #[test]
862 fn test_virt_to_phys_unmapped_address() {
863 let ops = MockTableOps::new();
864 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
867 assert!(result.is_none(), "Should return None for unmapped address");
868 }
869
870 #[test]
871 fn test_virt_to_phys_partially_mapped() {
872 let ops = MockTableOps::new();
873 let mapping = Mapping {
874 phys_base: 0x1000,
875 virt_base: 0x1000,
876 len: PAGE_SIZE as u64,
877 kind: MappingKind::Basic(BasicMapping {
878 readable: true,
879 writable: true,
880 executable: false,
881 }),
882 user_accessible: false,
883 };
884
885 unsafe { map(&ops, mapping) };
886
887 let result = unsafe { virt_to_phys(&ops, 0x5000, 1).next() };
889 assert!(
890 result.is_none(),
891 "Should return None for unmapped address in same PT"
892 );
893 }
894
895 #[test]
898 fn test_modify_pte_iterator_single_page() {
899 let ops = MockTableOps::new();
900 let request = MapRequest {
901 table_base: ops.root_table(),
902 vmin: 0x1000,
903 len: PAGE_SIZE as u64,
904 update_parent: UpdateParentNone {},
905 };
906
907 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
908 assert_eq!(responses.len(), 1, "Single page should yield one response");
909 assert_eq!(responses[0].vmin, 0x1000);
910 assert_eq!(responses[0].len, PAGE_SIZE as u64);
911 }
912
913 #[test]
914 fn test_modify_pte_iterator_multiple_pages() {
915 let ops = MockTableOps::new();
916 let request = MapRequest {
917 table_base: ops.root_table(),
918 vmin: 0x1000,
919 len: 3 * PAGE_SIZE as u64,
920 update_parent: UpdateParentNone {},
921 };
922
923 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
924 assert_eq!(responses.len(), 3, "3 pages should yield 3 responses");
925 }
926
927 #[test]
928 fn test_modify_pte_iterator_zero_length() {
929 let ops = MockTableOps::new();
930 let request = MapRequest {
931 table_base: ops.root_table(),
932 vmin: 0x1000,
933 len: 0,
934 update_parent: UpdateParentNone {},
935 };
936
937 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
938 assert_eq!(responses.len(), 0, "Zero length should yield no responses");
939 }
940
941 #[test]
942 fn test_modify_pte_iterator_unaligned_start() {
943 let ops = MockTableOps::new();
944 let request = MapRequest {
947 table_base: ops.root_table(),
948 vmin: 0x1800,
949 len: 0x1000,
950 update_parent: UpdateParentNone {},
951 };
952
953 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
954 assert_eq!(
955 responses.len(),
956 2,
957 "Unaligned mapping spanning 2 pages should yield 2 responses"
958 );
959 assert_eq!(responses[0].vmin, 0x1800);
960 assert_eq!(responses[0].len, 0x800); assert_eq!(responses[1].vmin, 0x2000);
962 assert_eq!(responses[1].len, 0x800); }
964
965 #[test]
968 fn test_entry_addr_from_table_base() {
969 let result = MockTableOps::entry_addr((2, 0), 40);
972 assert_eq!(result, (2, 5), "Should return (table 2, entry 5)");
973 }
974
975 #[test]
976 fn test_entry_addr_with_nonzero_base_entry() {
977 let result = MockTableOps::entry_addr((1, 10), 16);
983 assert_eq!(result, (1, 12), "Should add offset to base entry");
984 }
985
986 #[test]
987 fn test_to_phys_from_phys_roundtrip() {
988 let addr = (3, 42);
990 let phys = MockTableOps::to_phys(addr);
991 let back = MockTableOps::from_phys(phys);
992 assert_eq!(back, addr, "to_phys/from_phys should roundtrip");
993 }
994}