1use crate::vmem::{
29 BasicMapping, CowMapping, MapRequest, MapResponse, Mapping, MappingKind, TableMovabilityBase,
30 TableOps, TableReadOps, UpdateParent, UpdateParentNone, UpdateParentTable, Void, modify_ptes,
31 write_entry_updating,
32};
33
34#[derive(Copy, Clone)]
35pub(in crate::vmem) struct UpdateParentRoot {}
36
37#[inline(always)]
45#[allow(clippy::useless_conversion)]
46pub(super) unsafe fn read_pte_if_present<Op: TableReadOps>(
47 op: &Op,
48 entry_ptr: Op::TableAddr,
49) -> Option<u64> {
50 let pte: u64 = unsafe { op.read_entry(entry_ptr) }.into();
51 if (pte & PAGE_PRESENT) != 0 {
52 Some(pte)
53 } else {
54 None
55 }
56}
57
58pub(super) unsafe fn require_pte_exist<Op: TableReadOps, P: UpdateParent<Op>>(
63 op: &Op,
64 x: MapResponse<Op, P>,
65) -> Option<MapRequest<Op, P::ChildType>>
66where
67 P::ChildType: UpdateParent<Op>,
68{
69 unsafe { read_pte_if_present(op, x.entry_ptr) }.map(|pte| MapRequest {
70 #[allow(clippy::unnecessary_cast)]
71 table_base: Op::from_phys((pte & PTE_ADDR_MASK) as PhysAddr),
72 vmin: x.vmin,
73 len: x.len,
74 update_parent: x.update_parent.for_child_at_entry(x.entry_ptr),
75 })
76}
77
78pub const PAGE_PRESENT: u64 = 1;
99const PAGE_RW: u64 = 1 << 1;
101const PAGE_NX: u64 = 1 << 63;
103pub const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
106const 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;
116const PAGE_AVL_COW: u64 = 1 << 9;
117
118#[inline(always)]
120const fn page_rw_flag(writable: bool) -> u64 {
121 if writable { PAGE_RW } else { 0 }
122}
123
124#[inline(always)]
126const fn page_nx_flag(executable: bool) -> u64 {
127 if executable { 0 } else { PAGE_NX }
128}
129
130#[allow(clippy::identity_op)]
132#[allow(clippy::precedence)]
133fn pte_for_table<Op: TableOps>(table_addr: Op::TableAddr) -> u64 {
134 Op::to_phys(table_addr) |
135 PAGE_ACCESSED_SET | PAGE_CACHE_ENABLED | PAGE_WRITE_BACK | PAGE_USER_ACCESS_DISABLED |PAGE_RW | PAGE_PRESENT }
142
143pub(in crate::vmem) trait TableMovability<Op: TableReadOps + ?Sized, TableMoveInfo> {
147 type RootUpdateParent: UpdateParent<Op, TableMoveInfo = TableMoveInfo>;
148 fn root_update_parent() -> Self::RootUpdateParent;
149}
150impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> TableMovability<Op, Op::TableAddr>
151 for crate::vmem::MayMoveTable
152{
153 type RootUpdateParent = UpdateParentRoot;
154 fn root_update_parent() -> Self::RootUpdateParent {
155 UpdateParentRoot {}
156 }
157}
158impl<Op: TableReadOps> TableMovability<Op, Void> for crate::vmem::MayNotMoveTable {
159 type RootUpdateParent = UpdateParentNone;
160 fn root_update_parent() -> Self::RootUpdateParent {
161 UpdateParentNone {}
162 }
163}
164
165impl<
166 Op: TableOps<TableMovability = crate::vmem::MayMoveTable>,
167 P: UpdateParent<Op, TableMoveInfo = Op::TableAddr>,
168> UpdateParent<Op> for UpdateParentTable<Op, P>
169{
170 type TableMoveInfo = Op::TableAddr;
171 type ChildType = UpdateParentTable<Op, Self>;
172 fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
173 let pte = pte_for_table::<Op>(new_ptr);
174 unsafe {
175 write_entry_updating(op, self.parent, self.entry_ptr, pte);
176 }
177 }
178 fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
179 Self::ChildType::new(self, entry_ptr)
180 }
181}
182
183impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> UpdateParent<Op>
184 for UpdateParentRoot
185{
186 type TableMoveInfo = Op::TableAddr;
187 type ChildType = UpdateParentTable<Op, Self>;
188 fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
189 unsafe {
190 op.update_root(new_ptr);
191 }
192 }
193 fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
194 Self::ChildType::new(self, entry_ptr)
195 }
196}
197
198unsafe fn alloc_pte_if_needed<
203 Op: TableOps,
204 P: UpdateParent<
205 Op,
206 TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
207 >,
208>(
209 op: &Op,
210 x: MapResponse<Op, P>,
211) -> MapRequest<Op, P::ChildType>
212where
213 P::ChildType: UpdateParent<Op>,
214{
215 let new_update_parent = x.update_parent.for_child_at_entry(x.entry_ptr);
216 if let Some(pte) = unsafe { read_pte_if_present(op, x.entry_ptr) } {
217 return MapRequest {
218 table_base: Op::from_phys(pte & PTE_ADDR_MASK),
219 vmin: x.vmin,
220 len: x.len,
221 update_parent: new_update_parent,
222 };
223 }
224
225 let page_addr = unsafe { op.alloc_table() };
226
227 let pte = pte_for_table::<Op>(page_addr);
228 unsafe {
229 write_entry_updating(op, x.update_parent, x.entry_ptr, pte);
230 };
231 MapRequest {
232 table_base: page_addr,
233 vmin: x.vmin,
234 len: x.len,
235 update_parent: new_update_parent,
236 }
237}
238
239#[allow(clippy::identity_op)]
244#[allow(clippy::precedence)]
245unsafe fn map_page<
246 Op: TableOps,
247 P: UpdateParent<
248 Op,
249 TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
250 >,
251>(
252 op: &Op,
253 mapping: &Mapping,
254 r: MapResponse<Op, P>,
255) {
256 let pte = match &mapping.kind {
257 MappingKind::Basic(bm) =>
258 {
266 (mapping.phys_base + (r.vmin - mapping.virt_base)) |
267 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 }
277 MappingKind::Cow(cm) => {
278 (mapping.phys_base + (r.vmin - mapping.virt_base)) |
279 page_nx_flag(cm.executable) | PAGE_AVL_COW |
281 PAGE_PAT_WB | PAGE_DIRTY_SET | PAGE_ACCESSED_SET | PAGE_CACHE_ENABLED | PAGE_WRITE_BACK | PAGE_USER_ACCESS_DISABLED | 0 | PAGE_PRESENT }
290 MappingKind::Unmapped => 0,
291 };
292 unsafe {
293 write_entry_updating(op, r.update_parent, r.entry_ptr, pte);
294 }
295}
296
297#[allow(clippy::missing_safety_doc)]
318pub unsafe fn walk_va_spaces<Op: TableReadOps>(
319 op: &Op,
320 roots: &[Op::TableAddr],
321 address: u64,
322 len: u64,
323) -> ::alloc::vec::Vec<(
324 crate::vmem::SpaceId,
325 ::alloc::vec::Vec<crate::vmem::SpaceAwareMapping>,
326)> {
327 use ::alloc::vec::Vec;
328
329 let mut out: Vec<(crate::vmem::SpaceId, Vec<crate::vmem::SpaceAwareMapping>)> =
330 Vec::with_capacity(roots.len());
331
332 let addr = address & ((1u64 << VA_BITS) - 1);
333 let vmin = addr & !(PAGE_SIZE as u64 - 1);
334 let vmax = core::cmp::min(addr + len, 1u64 << VA_BITS);
335
336 for &root in roots {
337 #[allow(clippy::unnecessary_cast)]
338 let root_id: crate::vmem::SpaceId = Op::to_phys(root) as u64;
339 let mut mappings: Vec<crate::vmem::SpaceAwareMapping> = Vec::new();
340
341 let iter = modify_ptes::<47, 39, Op, _>(MapRequest {
342 table_base: root,
343 vmin,
344 len: vmax.saturating_sub(vmin),
345 update_parent: UpdateParentNone {},
346 })
347 .filter_map(|r| unsafe { require_pte_exist(op, r) })
348 .flat_map(modify_ptes::<38, 30, Op, _>)
349 .filter_map(|r| unsafe { require_pte_exist(op, r) })
350 .flat_map(modify_ptes::<29, 21, Op, _>)
351 .filter_map(|r| unsafe { require_pte_exist(op, r) })
352 .flat_map(modify_ptes::<20, 12, Op, _>);
353
354 for r in iter {
355 let Some(pte) = (unsafe { read_pte_if_present(op, r.entry_ptr) }) else {
356 continue;
357 };
358 let phys_addr = pte & PTE_ADDR_MASK;
359 let sgn_bit = r.vmin >> (VA_BITS - 1);
360 let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
361 let virt_addr = sgn_bits | r.vmin;
362
363 let executable = (pte & PAGE_NX) == 0;
364 let avl = pte & PTE_AVL_MASK;
365 let kind = if avl == PAGE_AVL_COW {
366 MappingKind::Cow(CowMapping {
367 readable: true,
368 executable,
369 })
370 } else {
371 MappingKind::Basic(BasicMapping {
372 readable: true,
373 writable: (pte & PAGE_RW) != 0,
374 executable,
375 })
376 };
377 mappings.push(crate::vmem::SpaceAwareMapping::ThisSpace(Mapping {
378 phys_base: phys_addr,
379 virt_base: virt_addr,
380 len: PAGE_SIZE as u64,
381 kind,
382 }));
383 }
384
385 out.push((root_id, mappings));
386 }
387
388 out
389}
390
391#[allow(clippy::missing_safety_doc)]
395pub unsafe fn space_aware_map<Op: TableOps>(
396 _op: &Op,
397 _ref_map: crate::vmem::SpaceReferenceMapping,
398 _built_roots: &::alloc::collections::BTreeMap<crate::vmem::SpaceId, Op::TableAddr>,
399) {
400}
401
402#[allow(clippy::missing_safety_doc)]
403pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
404 modify_ptes::<47, 39, Op, _>(MapRequest {
405 table_base: op.root_table(),
406 vmin: mapping.virt_base,
407 len: mapping.len,
408 update_parent: Op::TableMovability::root_update_parent(),
409 })
410 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
411 .flat_map(modify_ptes::<38, 30, Op, _>)
412 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
413 .flat_map(modify_ptes::<29, 21, Op, _>)
414 .map(|r| unsafe { alloc_pte_if_needed(op, r) })
415 .flat_map(modify_ptes::<20, 12, Op, _>)
416 .map(|r| unsafe { map_page(op, &mapping, r) })
417 .for_each(drop);
418}
419
420#[allow(clippy::missing_safety_doc)]
438pub unsafe fn virt_to_phys<'a, Op: TableReadOps + 'a>(
439 op: impl core::convert::AsRef<Op> + Copy + 'a,
440 address: u64,
441 len: u64,
442) -> impl Iterator<Item = Mapping> + 'a {
443 let addr = address & ((1u64 << VA_BITS) - 1);
445 let vmin = addr & !(PAGE_SIZE as u64 - 1);
447 let vmax = core::cmp::min(addr + len, 1u64 << VA_BITS);
450 modify_ptes::<47, 39, Op, _>(MapRequest {
451 table_base: op.as_ref().root_table(),
452 vmin,
453 len: vmax - vmin,
454 update_parent: UpdateParentNone {},
455 })
456 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
457 .flat_map(modify_ptes::<38, 30, Op, _>)
458 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
459 .flat_map(modify_ptes::<29, 21, Op, _>)
460 .filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
461 .flat_map(modify_ptes::<20, 12, Op, _>)
462 .filter_map(move |r| {
463 let pte = unsafe { read_pte_if_present(op.as_ref(), r.entry_ptr) }?;
464 let phys_addr = pte & PTE_ADDR_MASK;
465 let sgn_bit = r.vmin >> (VA_BITS - 1);
467 let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
468 let virt_addr = sgn_bits | r.vmin;
469
470 let executable = (pte & PAGE_NX) == 0;
471 let avl = pte & PTE_AVL_MASK;
472 let kind = if avl == PAGE_AVL_COW {
473 MappingKind::Cow(CowMapping {
474 readable: true,
475 executable,
476 })
477 } else {
478 MappingKind::Basic(BasicMapping {
479 readable: true,
480 writable: (pte & PAGE_RW) != 0,
481 executable,
482 })
483 };
484 Some(Mapping {
485 phys_base: phys_addr,
486 virt_base: virt_addr,
487 len: PAGE_SIZE as u64,
488 kind,
489 })
490 })
491}
492
493const VA_BITS: usize = 48; pub const PAGE_SIZE: usize = 4096;
496pub const PAGE_TABLE_SIZE: usize = 4096;
497pub type PageTableEntry = u64;
498pub type VirtAddr = u64;
499pub type PhysAddr = u64;
500
501#[cfg(test)]
502mod tests {
503 use alloc::vec;
504 use alloc::vec::Vec;
505 use core::cell::RefCell;
506
507 use super::*;
508 use crate::vmem::{
509 BasicMapping, Mapping, MappingKind, MayNotMoveTable, PAGE_TABLE_ENTRIES_PER_TABLE,
510 TableOps, TableReadOps, Void, bits,
511 };
512
513 struct MockTableOps {
516 tables: RefCell<Vec<[u64; PAGE_TABLE_ENTRIES_PER_TABLE]>>,
517 }
518
519 impl core::convert::AsRef<MockTableOps> for MockTableOps {
521 fn as_ref(&self) -> &Self {
522 self
523 }
524 }
525
526 impl MockTableOps {
527 fn new() -> Self {
528 Self {
530 tables: RefCell::new(vec![[0u64; PAGE_TABLE_ENTRIES_PER_TABLE]]),
531 }
532 }
533
534 fn table_count(&self) -> usize {
535 self.tables.borrow().len()
536 }
537
538 fn get_entry(&self, table_idx: usize, entry_idx: usize) -> u64 {
539 self.tables.borrow()[table_idx][entry_idx]
540 }
541 }
542
543 impl TableReadOps for MockTableOps {
544 type TableAddr = (usize, usize); fn entry_addr(addr: Self::TableAddr, entry_offset: u64) -> Self::TableAddr {
547 let phys = Self::to_phys(addr) + entry_offset;
549 Self::from_phys(phys)
550 }
551
552 unsafe fn read_entry(&self, addr: Self::TableAddr) -> u64 {
553 self.tables.borrow()[addr.0][addr.1]
554 }
555
556 fn to_phys(addr: Self::TableAddr) -> PhysAddr {
557 (addr.0 as u64 * PAGE_TABLE_SIZE as u64) + (addr.1 as u64 * 8)
559 }
560
561 fn from_phys(addr: PhysAddr) -> Self::TableAddr {
562 let table_idx = (addr / PAGE_TABLE_SIZE as u64) as usize;
563 let entry_idx = ((addr % PAGE_TABLE_SIZE as u64) / 8) as usize;
564 (table_idx, entry_idx)
565 }
566
567 fn root_table(&self) -> Self::TableAddr {
568 (0, 0)
569 }
570 }
571
572 impl TableOps for MockTableOps {
573 type TableMovability = MayNotMoveTable;
574
575 unsafe fn alloc_table(&self) -> Self::TableAddr {
576 let mut tables = self.tables.borrow_mut();
577 let idx = tables.len();
578 tables.push([0u64; PAGE_TABLE_ENTRIES_PER_TABLE]);
579 (idx, 0)
580 }
581
582 unsafe fn write_entry(&self, addr: Self::TableAddr, entry: u64) -> Option<Void> {
583 self.tables.borrow_mut()[addr.0][addr.1] = entry;
584 None
585 }
586
587 unsafe fn update_root(&self, impossible: Void) {
588 match impossible {}
589 }
590 }
591
592 #[test]
595 fn test_bits_extracts_pml4_index() {
596 let addr: u64 = 0x0000_0080_0000_0000;
599 assert_eq!(bits::<47, 39>(addr), 1);
600 }
601
602 #[test]
603 fn test_bits_extracts_pdpt_index() {
604 let addr: u64 = 0x4000_0000;
607 assert_eq!(bits::<38, 30>(addr), 1);
608 }
609
610 #[test]
611 fn test_bits_extracts_pd_index() {
612 let addr: u64 = 0x0000_0000_0020_0000;
615 assert_eq!(bits::<29, 21>(addr), 1);
616 }
617
618 #[test]
619 fn test_bits_extracts_pt_index() {
620 let addr: u64 = 0x0000_0000_0000_1000;
623 assert_eq!(bits::<20, 12>(addr), 1);
624 }
625
626 #[test]
627 fn test_bits_max_index() {
628 let addr: u64 = 0x0000_FF80_0000_0000;
631 assert_eq!(bits::<47, 39>(addr), 511);
632 }
633
634 #[test]
637 fn test_page_rw_flag_writable() {
638 assert_eq!(page_rw_flag(true), PAGE_RW);
639 }
640
641 #[test]
642 fn test_page_rw_flag_readonly() {
643 assert_eq!(page_rw_flag(false), 0);
644 }
645
646 #[test]
647 fn test_page_nx_flag_executable() {
648 assert_eq!(page_nx_flag(true), 0); }
650
651 #[test]
652 fn test_page_nx_flag_not_executable() {
653 assert_eq!(page_nx_flag(false), PAGE_NX);
654 }
655
656 #[test]
659 fn test_map_single_page() {
660 let ops = MockTableOps::new();
661 let mapping = Mapping {
662 phys_base: 0x1000,
663 virt_base: 0x1000,
664 len: PAGE_SIZE as u64,
665 kind: MappingKind::Basic(BasicMapping {
666 readable: true,
667 writable: true,
668 executable: false,
669 }),
670 };
671
672 unsafe { map(&ops, mapping) };
673
674 assert_eq!(ops.table_count(), 4);
676
677 let pml4_entry = ops.get_entry(0, 0);
679 assert_ne!(pml4_entry & PAGE_PRESENT, 0, "PML4 entry should be present");
680 assert_ne!(pml4_entry & PAGE_RW, 0, "PML4 entry should be writable");
681
682 let pte = ops.get_entry(3, 1);
685 assert_ne!(pte & PAGE_PRESENT, 0, "PTE should be present");
686 assert_ne!(pte & PAGE_RW, 0, "PTE should be writable");
687 assert_ne!(pte & PAGE_NX, 0, "PTE should have NX set (not executable)");
688 assert_eq!(pte & PTE_ADDR_MASK, 0x1000, "PTE should map to phys 0x1000");
689 }
690
691 #[test]
692 fn test_map_executable_page() {
693 let ops = MockTableOps::new();
694 let mapping = Mapping {
695 phys_base: 0x2000,
696 virt_base: 0x2000,
697 len: PAGE_SIZE as u64,
698 kind: MappingKind::Basic(BasicMapping {
699 readable: true,
700 writable: false,
701 executable: true,
702 }),
703 };
704
705 unsafe { map(&ops, mapping) };
706
707 let pte = ops.get_entry(3, 2);
709 assert_ne!(pte & PAGE_PRESENT, 0, "PTE should be present");
710 assert_eq!(pte & PAGE_RW, 0, "PTE should be read-only");
711 assert_eq!(pte & PAGE_NX, 0, "PTE should NOT have NX set (executable)");
712 }
713
714 #[test]
715 fn test_map_multiple_pages() {
716 let ops = MockTableOps::new();
717 let mapping = Mapping {
718 phys_base: 0x10000,
719 virt_base: 0x10000,
720 len: 4 * PAGE_SIZE as u64, kind: MappingKind::Basic(BasicMapping {
722 readable: true,
723 writable: true,
724 executable: false,
725 }),
726 };
727
728 unsafe { map(&ops, mapping) };
729
730 for i in 0..4 {
732 let entry_idx = 16 + i; let pte = ops.get_entry(3, entry_idx);
734 assert_ne!(pte & PAGE_PRESENT, 0, "PTE {} should be present", i);
735 let expected_phys = 0x10000 + (i as u64 * PAGE_SIZE as u64);
736 assert_eq!(
737 pte & PTE_ADDR_MASK,
738 expected_phys,
739 "PTE {} should map to correct phys addr",
740 i
741 );
742 }
743 }
744
745 #[test]
746 fn test_map_reuses_existing_tables() {
747 let ops = MockTableOps::new();
748
749 let mapping1 = Mapping {
751 phys_base: 0x1000,
752 virt_base: 0x1000,
753 len: PAGE_SIZE as u64,
754 kind: MappingKind::Basic(BasicMapping {
755 readable: true,
756 writable: true,
757 executable: false,
758 }),
759 };
760 unsafe { map(&ops, mapping1) };
761 let tables_after_first = ops.table_count();
762
763 let mapping2 = Mapping {
765 phys_base: 0x5000,
766 virt_base: 0x5000,
767 len: PAGE_SIZE as u64,
768 kind: MappingKind::Basic(BasicMapping {
769 readable: true,
770 writable: true,
771 executable: false,
772 }),
773 };
774 unsafe { map(&ops, mapping2) };
775
776 assert_eq!(
778 ops.table_count(),
779 tables_after_first,
780 "Should reuse existing page tables"
781 );
782 }
783
784 #[test]
787 fn test_virt_to_phys_mapped_address() {
788 let ops = MockTableOps::new();
789 let mapping = Mapping {
790 phys_base: 0x1000,
791 virt_base: 0x1000,
792 len: PAGE_SIZE as u64,
793 kind: MappingKind::Basic(BasicMapping {
794 readable: true,
795 writable: true,
796 executable: false,
797 }),
798 };
799
800 unsafe { map(&ops, mapping) };
801
802 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
803 assert!(result.is_some(), "Should find mapped address");
804 let mapping = result.unwrap();
805 assert_eq!(mapping.phys_base, 0x1000);
806 }
807
808 #[test]
809 fn test_virt_to_phys_unaligned_virt() {
810 let ops = MockTableOps::new();
811 let mapping = Mapping {
812 phys_base: 0x1000,
813 virt_base: 0x1000,
814 len: PAGE_SIZE as u64,
815 kind: MappingKind::Basic(BasicMapping {
816 readable: true,
817 writable: true,
818 executable: false,
819 }),
820 };
821
822 unsafe { map(&ops, mapping) };
823
824 let result = unsafe { virt_to_phys(&ops, 0x1234, 1).next() };
825 assert!(result.is_some(), "Should find mapped address");
826 let mapping = result.unwrap();
827 assert_eq!(mapping.phys_base, 0x1000);
828 }
829
830 #[test]
831 fn test_virt_to_phys_unaligned_virt_and_across_pages_len() {
832 let ops = MockTableOps::new();
833 let mapping = Mapping {
834 phys_base: 0x1000,
835 virt_base: 0x1000,
836 len: 2 * PAGE_SIZE as u64, kind: MappingKind::Basic(BasicMapping {
838 readable: true,
839 writable: true,
840 executable: false,
841 }),
842 };
843
844 unsafe { map(&ops, mapping) };
845
846 let mappings = unsafe { virt_to_phys(&ops, 0x1F00, 0x300).collect::<Vec<_>>() };
847 assert_eq!(mappings.len(), 2, "Should return 2 mappings for 2 pages");
848 assert_eq!(mappings[0].phys_base, 0x1000);
849 assert_eq!(mappings[1].phys_base, 0x2000);
850 }
851
852 #[test]
853 fn test_virt_to_phys_unaligned_virt_and_multiple_page_len() {
854 let ops = MockTableOps::new();
855 let mapping = Mapping {
856 phys_base: 0x1000,
857 virt_base: 0x1000,
858 len: PAGE_SIZE as u64 * 2 + 0x200, kind: MappingKind::Basic(BasicMapping {
860 readable: true,
861 writable: true,
862 executable: false,
863 }),
864 };
865
866 unsafe { map(&ops, mapping) };
867
868 let mappings =
869 unsafe { virt_to_phys(&ops, 0x1234, PAGE_SIZE as u64 * 2 + 0x10).collect::<Vec<_>>() };
870 assert_eq!(mappings.len(), 3, "Should return 3 mappings for 3 pages");
871 assert_eq!(mappings[0].phys_base, 0x1000);
872 assert_eq!(mappings[1].phys_base, 0x2000);
873 assert_eq!(mappings[2].phys_base, 0x3000);
874 }
875
876 #[test]
877 fn test_virt_to_phys_perms() {
878 let test = |kind| {
879 let ops = MockTableOps::new();
880 let mapping = Mapping {
881 phys_base: 0x1000,
882 virt_base: 0x1000,
883 len: PAGE_SIZE as u64,
884 kind,
885 };
886 unsafe { map(&ops, mapping) };
887 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
888 let mapping = result.unwrap();
889 assert_eq!(mapping.kind, kind);
890 };
891 test(MappingKind::Basic(BasicMapping {
892 readable: true,
893 writable: false,
894 executable: false,
895 }));
896 test(MappingKind::Basic(BasicMapping {
897 readable: true,
898 writable: false,
899 executable: true,
900 }));
901 test(MappingKind::Basic(BasicMapping {
902 readable: true,
903 writable: true,
904 executable: false,
905 }));
906 test(MappingKind::Basic(BasicMapping {
907 readable: true,
908 writable: true,
909 executable: true,
910 }));
911 test(MappingKind::Cow(CowMapping {
912 readable: true,
913 executable: false,
914 }));
915 test(MappingKind::Cow(CowMapping {
916 readable: true,
917 executable: true,
918 }));
919 }
920
921 #[test]
922 fn test_virt_to_phys_unmapped_address() {
923 let ops = MockTableOps::new();
924 let result = unsafe { virt_to_phys(&ops, 0x1000, 1).next() };
927 assert!(result.is_none(), "Should return None for unmapped address");
928 }
929
930 #[test]
931 fn test_virt_to_phys_partially_mapped() {
932 let ops = MockTableOps::new();
933 let mapping = Mapping {
934 phys_base: 0x1000,
935 virt_base: 0x1000,
936 len: PAGE_SIZE as u64,
937 kind: MappingKind::Basic(BasicMapping {
938 readable: true,
939 writable: true,
940 executable: false,
941 }),
942 };
943
944 unsafe { map(&ops, mapping) };
945
946 let result = unsafe { virt_to_phys(&ops, 0x5000, 1).next() };
948 assert!(
949 result.is_none(),
950 "Should return None for unmapped address in same PT"
951 );
952 }
953
954 #[test]
957 fn test_modify_pte_iterator_single_page() {
958 let ops = MockTableOps::new();
959 let request = MapRequest {
960 table_base: ops.root_table(),
961 vmin: 0x1000,
962 len: PAGE_SIZE as u64,
963 update_parent: UpdateParentNone {},
964 };
965
966 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
967 assert_eq!(responses.len(), 1, "Single page should yield one response");
968 assert_eq!(responses[0].vmin, 0x1000);
969 assert_eq!(responses[0].len, PAGE_SIZE as u64);
970 }
971
972 #[test]
973 fn test_modify_pte_iterator_multiple_pages() {
974 let ops = MockTableOps::new();
975 let request = MapRequest {
976 table_base: ops.root_table(),
977 vmin: 0x1000,
978 len: 3 * PAGE_SIZE as u64,
979 update_parent: UpdateParentNone {},
980 };
981
982 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
983 assert_eq!(responses.len(), 3, "3 pages should yield 3 responses");
984 }
985
986 #[test]
987 fn test_modify_pte_iterator_zero_length() {
988 let ops = MockTableOps::new();
989 let request = MapRequest {
990 table_base: ops.root_table(),
991 vmin: 0x1000,
992 len: 0,
993 update_parent: UpdateParentNone {},
994 };
995
996 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
997 assert_eq!(responses.len(), 0, "Zero length should yield no responses");
998 }
999
1000 #[test]
1001 fn test_modify_pte_iterator_unaligned_start() {
1002 let ops = MockTableOps::new();
1003 let request = MapRequest {
1006 table_base: ops.root_table(),
1007 vmin: 0x1800,
1008 len: 0x1000,
1009 update_parent: UpdateParentNone {},
1010 };
1011
1012 let responses: Vec<_> = modify_ptes::<20, 12, MockTableOps, _>(request).collect();
1013 assert_eq!(
1014 responses.len(),
1015 2,
1016 "Unaligned mapping spanning 2 pages should yield 2 responses"
1017 );
1018 assert_eq!(responses[0].vmin, 0x1800);
1019 assert_eq!(responses[0].len, 0x800); assert_eq!(responses[1].vmin, 0x2000);
1021 assert_eq!(responses[1].len, 0x800); }
1023
1024 #[test]
1027 fn test_entry_addr_from_table_base() {
1028 let result = MockTableOps::entry_addr((2, 0), 40);
1031 assert_eq!(result, (2, 5), "Should return (table 2, entry 5)");
1032 }
1033
1034 #[test]
1035 fn test_entry_addr_with_nonzero_base_entry() {
1036 let result = MockTableOps::entry_addr((1, 10), 16);
1042 assert_eq!(result, (1, 12), "Should add offset to base entry");
1043 }
1044
1045 #[test]
1046 fn test_to_phys_from_phys_roundtrip() {
1047 let addr = (3, 42);
1049 let phys = MockTableOps::to_phys(addr);
1050 let back = MockTableOps::from_phys(phys);
1051 assert_eq!(back, addr, "to_phys/from_phys should roundtrip");
1052 }
1053}