1use super::root_context::AddressWidth;
20use bitfield_struct::bitfield;
21use inspect::Inspect;
22use open_enum::open_enum;
23use zerocopy::FromBytes;
24use zerocopy::Immutable;
25use zerocopy::IntoBytes;
26use zerocopy::KnownLayout;
27
28pub const SCALABLE_CONTEXT_TABLE_ENTRIES: usize = 128;
30pub const PASID_TABLE_ENTRIES: u32 = 64;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
35pub enum ScalableValidationError {
36 #[error("entry not present")]
38 NotPresent,
39 #[error("reserved bits {bits:#x} in word {word}")]
41 ReservedBits {
42 word: usize,
44 bits: u64,
46 },
47 #[error("unsupported fields {bits:#x} in word {word}")]
49 UnsupportedFields {
50 word: usize,
52 bits: u64,
54 },
55 #[error("unsupported PASID translation type {0:?}")]
57 UnsupportedTranslationType(PasidTranslationType),
58 #[error("unsupported second-stage address width {0:?}")]
60 UnsupportedAddressWidth(AddressWidth),
61 #[error("PASID {pasid:#x} exceeds directory limit {limit:#x} (exclusive)")]
63 PasidOutOfRange {
64 pasid: u32,
66 limit: u32,
68 },
69 #[error("invalid PASID directory size encoding {0}")]
71 InvalidDirectorySize(u8),
72 #[error("invalid host address width {0}")]
74 InvalidHostAddressWidth(u8),
75}
76
77fn address_reserved_mask(haw: u8) -> Result<u64, ScalableValidationError> {
78 match haw {
79 12..=63 => Ok(u64::MAX << haw),
80 64 => Ok(0),
81 _ => Err(ScalableValidationError::InvalidHostAddressWidth(haw)),
82 }
83}
84
85fn reserved(word: usize, value: u64, mask: u64) -> Result<(), ScalableValidationError> {
86 let bits = value & mask;
87 if bits != 0 {
88 return Err(ScalableValidationError::ReservedBits { word, bits });
89 }
90 Ok(())
91}
92
93fn unsupported(word: usize, value: u64, mask: u64) -> Result<(), ScalableValidationError> {
94 let bits = value & mask;
95 if bits != 0 {
96 return Err(ScalableValidationError::UnsupportedFields { word, bits });
97 }
98 Ok(())
99}
100
101#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
106#[repr(C)]
107pub struct ScalableRootEntry {
108 pub lower: ScalableRootEntryHalf,
110 pub upper: ScalableRootEntryHalf,
112}
113
114#[bitfield(u64)]
116#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
117#[rustfmt::skip]
118pub struct ScalableRootEntryHalf {
119 pub p: bool,
121 #[bits(11)]
122 _reserved: u64,
123 #[bits(52)]
125 pub ctp: u64,
126}
127
128impl ScalableRootEntry {
129 pub fn half(&self, devfn: u8) -> ScalableRootEntryHalf {
131 if devfn < 128 { self.lower } else { self.upper }
132 }
133}
134
135impl ScalableRootEntryHalf {
136 pub fn context_table_address(&self) -> u64 {
138 self.ctp() << 12
139 }
140
141 pub fn validate(&self, haw: u8) -> Result<(), ScalableValidationError> {
145 if !self.p() {
146 return Err(ScalableValidationError::NotPresent);
147 }
148 reserved(0, self.into_bits(), 0xffe | address_reserved_mask(haw)?)
149 }
150}
151
152#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
154#[repr(C)]
155pub struct ScalableContextEntry {
156 pub lo: ScalableContextEntryLo,
158 pub hi: ScalableContextEntryHi,
160 pub reserved: [u64; 2],
162}
163
164#[bitfield(u64)]
166#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
167#[rustfmt::skip]
168pub struct ScalableContextEntryLo {
169 pub p: bool,
171 pub fpd: bool,
173 pub dte: bool,
175 pub paside: bool,
177 pub pre: bool,
179 #[bits(4)]
180 _reserved: u64,
181 #[bits(3)]
183 pub pdts: u8,
184 #[bits(52)]
186 pub pasiddirptr: u64,
187}
188
189#[bitfield(u64)]
191#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
192#[rustfmt::skip]
193pub struct ScalableContextEntryHi {
194 #[bits(20)]
196 pub rid_pasid: u32,
197 pub rid_priv: bool,
199 #[bits(43)]
200 _reserved: u64,
201}
202
203pub fn pasid_directory_entries(pdts: u8) -> Result<u32, ScalableValidationError> {
205 match pdts {
206 0..=7 => Ok(1u32 << (pdts + 7)),
207 _ => Err(ScalableValidationError::InvalidDirectorySize(pdts)),
208 }
209}
210
211impl ScalableContextEntry {
212 pub fn table_index(devfn: u8) -> usize {
214 usize::from(devfn & 0x7f)
215 }
216
217 pub fn directory_address(&self) -> u64 {
219 self.lo.pasiddirptr() << 12
220 }
221
222 pub fn pasid_indices(&self, pasid: u32) -> Result<(u16, u8), ScalableValidationError> {
227 let limit = pasid_directory_entries(self.lo.pdts())? * PASID_TABLE_ENTRIES;
228 if pasid >= limit {
229 return Err(ScalableValidationError::PasidOutOfRange { pasid, limit });
230 }
231 Ok(((pasid >> 6) as u16, (pasid & 0x3f) as u8))
232 }
233
234 pub fn validate(&self, haw: u8) -> Result<(), ScalableValidationError> {
236 if !self.lo.p() {
237 return Err(ScalableValidationError::NotPresent);
238 }
239 reserved(0, self.lo.into_bits(), 0x1e0 | address_reserved_mask(haw)?)?;
240 reserved(1, self.hi.into_bits(), u64::MAX << 21)?;
241 for (index, word) in self.reserved.iter().enumerate() {
242 reserved(index + 2, *word, u64::MAX)?;
243 }
244 unsupported(0, self.lo.into_bits(), 0x1c)?;
245 unsupported(1, self.hi.into_bits(), 1 << 20)?;
246 self.pasid_indices(self.hi.rid_pasid())?;
247 Ok(())
248 }
249}
250
251#[bitfield(u64)]
253#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
254#[rustfmt::skip]
255pub struct PasidDirectoryEntry {
256 pub p: bool,
258 pub fpd: bool,
260 #[bits(10)]
261 _reserved: u64,
262 #[bits(52)]
264 pub smptblptr: u64,
265}
266
267impl PasidDirectoryEntry {
268 pub fn table_address(&self) -> u64 {
270 self.smptblptr() << 12
271 }
272
273 pub fn validate(&self, haw: u8) -> Result<(), ScalableValidationError> {
275 if !self.p() {
276 return Err(ScalableValidationError::NotPresent);
277 }
278 reserved(0, self.into_bits(), 0xffc | address_reserved_mask(haw)?)
279 }
280}
281
282open_enum! {
283 #[derive(Inspect)]
285 #[inspect(debug)]
286 pub enum PasidTranslationType: u8 {
287 FIRST_STAGE = 0b001,
289 SECOND_STAGE = 0b010,
291 NESTED = 0b011,
293 PASS_THROUGH = 0b100,
295 }
296}
297
298#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
300#[repr(C)]
301pub struct PasidTableEntry {
302 pub lo: PasidTableEntryLo,
304 pub hi: PasidTableEntryHi,
306 pub first_stage: PasidTableEntryFirstStage,
308 pub reserved: [u64; 5],
310}
311
312#[bitfield(u64)]
314#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
315#[rustfmt::skip]
316pub struct PasidTableEntryLo {
317 pub p: bool,
319 pub fpd: bool,
321 #[bits(3)]
323 pub aw: u8,
324 #[bits(1)]
325 _reserved1: u64,
326 #[bits(3)]
328 pub pgtt: u8,
329 pub ssade: bool,
333 #[bits(2)]
334 _reserved2: u64,
335 #[bits(52)]
338 pub ssptptr: u64,
339}
340
341#[bitfield(u64)]
343#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
344#[rustfmt::skip]
345pub struct PasidTableEntryHi {
346 #[bits(16)]
348 pub did: u16,
349 #[bits(7)]
350 _reserved1: u64,
351 pub pwsnp: bool,
353 pub pgsnp: bool,
355 pub cd: bool,
357 pub emte: bool,
359 #[bits(5)]
360 _reserved2: u64,
361 #[bits(32)]
363 pub pat: u32,
364}
365
366#[bitfield(u64)]
368#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
369#[rustfmt::skip]
370pub struct PasidTableEntryFirstStage {
371 pub sre: bool,
373 #[bits(1)]
374 _reserved1: u64,
375 #[bits(2)]
377 pub fspm: u8,
378 pub wpe: bool,
380 #[bits(2)]
382 _ignored: u64,
383 pub eafe: bool,
385 #[bits(4)]
386 _reserved2: u64,
387 #[bits(52)]
389 pub fsptptr: u64,
390}
391
392impl PasidTableEntry {
393 pub fn page_table_address(&self) -> u64 {
395 self.lo.ssptptr() << 12
396 }
397
398 pub fn validate(&self, haw: u8) -> Result<(), ScalableValidationError> {
403 if !self.lo.p() {
404 return Err(ScalableValidationError::NotPresent);
405 }
406 let address_reserved = address_reserved_mask(haw)?;
407 reserved(0, self.lo.into_bits(), 0xc20 | address_reserved)?;
408 reserved(1, self.hi.into_bits(), 0xf87f_0000)?;
409 reserved(2, self.first_stage.into_bits(), 0xf02 | address_reserved)?;
410 for (index, word) in self.reserved.iter().enumerate() {
411 reserved(index + 3, *word, u64::MAX)?;
412 }
413 unsupported(1, self.hi.into_bits(), 0xffff_ffff_0600_0000)?;
414 unsupported(
415 2,
416 self.first_stage.into_bits(),
417 0x9d | ((u64::MAX << 12) & !address_reserved),
418 )?;
419 match PasidTranslationType(self.lo.pgtt()) {
420 PasidTranslationType::SECOND_STAGE => match AddressWidth(self.lo.aw()) {
421 AddressWidth::AW_39BIT | AddressWidth::AW_48BIT => Ok(()),
422 aw => Err(ScalableValidationError::UnsupportedAddressWidth(aw)),
423 },
424 PasidTranslationType::PASS_THROUGH => Ok(()),
425 pgtt => Err(ScalableValidationError::UnsupportedTranslationType(pgtt)),
426 }
427 }
428}
429
430const _: () = {
431 assert!(size_of::<ScalableRootEntry>() == 16);
432 assert!(size_of::<ScalableContextEntry>() == 32);
433 assert!(size_of::<PasidDirectoryEntry>() == 8);
434 assert!(size_of::<PasidTableEntry>() == 64);
435 assert!(align_of::<ScalableRootEntry>() == 8);
436 assert!(align_of::<ScalableContextEntry>() == 8);
437 assert!(align_of::<PasidDirectoryEntry>() == 8);
438 assert!(align_of::<PasidTableEntry>() == 8);
439};
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use test_with_tracing::test;
445
446 fn decode<T: FromBytes + KnownLayout + Immutable>(words: &[u64]) -> T {
447 let bytes: Vec<_> = words.iter().flat_map(|word| word.to_le_bytes()).collect();
448 T::read_from_bytes(&bytes).unwrap()
449 }
450
451 #[test]
452 fn test_sizes_alignment_and_offsets() {
453 assert_eq!(size_of::<ScalableRootEntry>(), 16);
454 assert_eq!(size_of::<ScalableRootEntryHalf>(), 8);
455 assert_eq!(size_of::<ScalableContextEntry>(), 32);
456 assert_eq!(size_of::<PasidDirectoryEntry>(), 8);
457 assert_eq!(size_of::<PasidTableEntry>(), 64);
458 assert_eq!(align_of::<ScalableRootEntry>(), 8);
459 assert_eq!(align_of::<ScalableContextEntry>(), 8);
460 assert_eq!(align_of::<PasidDirectoryEntry>(), 8);
461 assert_eq!(align_of::<PasidTableEntry>(), 8);
462 assert_eq!(std::mem::offset_of!(ScalableRootEntry, upper), 8);
463 assert_eq!(std::mem::offset_of!(ScalableContextEntry, hi), 8);
464 assert_eq!(std::mem::offset_of!(ScalableContextEntry, reserved), 16);
465 assert_eq!(std::mem::offset_of!(PasidTableEntry, hi), 8);
466 assert_eq!(std::mem::offset_of!(PasidTableEntry, first_stage), 16);
467 assert_eq!(std::mem::offset_of!(PasidTableEntry, reserved), 24);
468 assert_eq!(super::super::root_context::ROOT_TABLE_ENTRIES * 16, 4096);
469 assert_eq!(SCALABLE_CONTEXT_TABLE_ENTRIES * 32, 4096);
470 assert_eq!(PASID_TABLE_ENTRIES * 64, 4096);
471 }
472
473 #[test]
474 fn test_root_literal_bytes_and_half_boundaries() {
475 let entry: ScalableRootEntry = decode(&[0x1234_5001, 0xabcd_e001]);
476 assert!(entry.lower.p());
477 assert!(entry.upper.p());
478 assert_eq!(entry.lower.context_table_address(), 0x1234_5000);
479 assert_eq!(entry.upper.context_table_address(), 0xabcd_e000);
480 for devfn in 0..=255 {
481 let expected = if devfn < 128 {
482 0x1234_5000
483 } else {
484 0xabcd_e000
485 };
486 assert_eq!(entry.half(devfn).context_table_address(), expected);
487 assert_eq!(
488 ScalableContextEntry::table_index(devfn),
489 usize::from(devfn % 128)
490 );
491 }
492 for (devfn, offset) in [(0, 0), (127, 4064), (128, 0), (255, 4064)] {
493 assert_eq!(ScalableContextEntry::table_index(devfn) * 32, offset);
494 }
495 let built = ScalableRootEntry {
496 lower: ScalableRootEntryHalf::new().with_p(true).with_ctp(0x12345),
497 upper: ScalableRootEntryHalf::new().with_p(true).with_ctp(0xabcde),
498 };
499 assert_eq!(
500 built.as_bytes(),
501 &[
502 1, 0x50, 0x34, 0x12, 0, 0, 0, 0, 1, 0xe0, 0xcd, 0xab, 0, 0, 0, 0
503 ]
504 );
505 }
506
507 #[test]
508 fn test_root_reserved_bits_and_independent_presence() {
509 for bit in 1..64 {
510 let half = ScalableRootEntryHalf::from(1 | (1 << bit));
511 if (1..=11).contains(&bit) || bit >= 48 {
512 assert_eq!(
513 half.validate(48),
514 Err(ScalableValidationError::ReservedBits {
515 word: 0,
516 bits: 1 << bit
517 })
518 );
519 } else {
520 assert_eq!(half.validate(48), Ok(()));
521 }
522 }
523 for words in [[1, u64::MAX - 1], [u64::MAX - 1, 1]] {
524 let entry: ScalableRootEntry = decode(&words);
525 for devfn in [0, 127, 128, 255] {
526 let half = entry.half(devfn);
527 if half.p() {
528 assert_eq!(half.validate(48), Ok(()));
529 } else {
530 assert_eq!(half.validate(48), Err(ScalableValidationError::NotPresent));
531 }
532 }
533 }
534 }
535
536 #[test]
537 fn test_context_literal_words() {
538 let entry: ScalableContextEntry = decode(&[0x1234_5678_9a03, 0x12345, 0, 0]);
539 assert!(entry.lo.p());
540 assert!(entry.lo.fpd());
541 assert_eq!(entry.lo.pdts(), 5);
542 assert_eq!(entry.directory_address(), 0x1234_5678_9000);
543 assert_eq!(entry.hi.rid_pasid(), 0x12345);
544 assert!(!entry.hi.rid_priv());
545 assert_eq!(entry.validate(48), Ok(()));
546 assert_eq!(
547 ScalableContextEntryLo::new()
548 .with_p(true)
549 .with_fpd(true)
550 .with_pdts(5)
551 .with_pasiddirptr(0x1_2345_6789)
552 .into_bits(),
553 0x1234_5678_9a03
554 );
555 assert_eq!(
556 ScalableContextEntryHi::new()
557 .with_rid_pasid(0xabcde)
558 .with_rid_priv(true)
559 .into_bits(),
560 0x1abcde
561 );
562 assert!(ScalableContextEntryLo::from(1 << 2).dte());
563 assert!(ScalableContextEntryLo::from(1 << 3).paside());
564 assert!(ScalableContextEntryLo::from(1 << 4).pre());
565 }
566
567 #[test]
568 fn test_context_every_bit_validation() {
569 for bit in 0..256 {
570 let mut words = [1, 0, 0, 0];
571 words[bit / 64] ^= 1 << (bit % 64);
572 let entry: ScalableContextEntry = decode(&words);
573 let result = entry.validate(48);
574 match bit {
575 0 => assert_eq!(result, Err(ScalableValidationError::NotPresent)),
576 1 | 9..=47 | 64..=76 => assert_eq!(result, Ok(()), "bit {bit}"),
577 2..=4 | 84 => assert_eq!(
578 result,
579 Err(ScalableValidationError::UnsupportedFields {
580 word: bit / 64,
581 bits: 1 << (bit % 64),
582 })
583 ),
584 77..=83 => assert!(matches!(
585 result,
586 Err(ScalableValidationError::PasidOutOfRange { .. })
587 )),
588 _ => assert_eq!(
589 result,
590 Err(ScalableValidationError::ReservedBits {
591 word: bit / 64,
592 bits: 1 << (bit % 64),
593 })
594 ),
595 }
596 }
597 }
598
599 #[test]
600 fn test_pdts_and_pasid_boundaries() {
601 for pdts in 0..=7 {
602 let entries = 128 << pdts;
603 let limit = 8192 << pdts;
604 assert_eq!(pasid_directory_entries(pdts), Ok(entries));
605 let mut context: ScalableContextEntry = decode(&[1 | (u64::from(pdts) << 9), 0, 0, 0]);
606 assert_eq!(context.pasid_indices(0), Ok((0, 0)));
607 assert_eq!(context.pasid_indices(63), Ok((0, 63)));
608 assert_eq!(context.pasid_indices(64), Ok((1, 0)));
609 assert_eq!(
610 context.pasid_indices(limit - 1),
611 Ok(((entries - 1) as u16, 63))
612 );
613 for pasid in [limit, 1 << 20, u32::MAX] {
614 assert_eq!(
615 context.pasid_indices(pasid),
616 Err(ScalableValidationError::PasidOutOfRange { pasid, limit })
617 );
618 }
619 context.hi.set_rid_pasid(limit - 1);
620 assert_eq!(context.validate(48), Ok(()));
621 if pdts < 7 {
622 context.hi.set_rid_pasid(limit);
623 assert!(matches!(
624 context.validate(48),
625 Err(ScalableValidationError::PasidOutOfRange { .. })
626 ));
627 }
628 }
629 for pdts in 8..=u8::MAX {
630 assert_eq!(
631 pasid_directory_entries(pdts),
632 Err(ScalableValidationError::InvalidDirectorySize(pdts))
633 );
634 }
635 }
636
637 #[test]
638 fn test_directory_literal_bytes_and_validation() {
639 let entry: PasidDirectoryEntry = decode(&[0x9876_5432_1003]);
640 assert!(entry.p());
641 assert!(entry.fpd());
642 assert_eq!(entry.table_address(), 0x9876_5432_1000);
643 assert_eq!(entry.validate(48), Ok(()));
644 assert_eq!(
645 PasidDirectoryEntry::new()
646 .with_p(true)
647 .with_fpd(true)
648 .with_smptblptr(0x9_8765_4321)
649 .as_bytes(),
650 &[3, 0x10, 0x32, 0x54, 0x76, 0x98, 0, 0]
651 );
652 for bit in 1..64 {
653 let entry = PasidDirectoryEntry::from(1 | (1 << bit));
654 if (2..=11).contains(&bit) || bit >= 48 {
655 assert_eq!(
656 entry.validate(48),
657 Err(ScalableValidationError::ReservedBits {
658 word: 0,
659 bits: 1 << bit,
660 })
661 );
662 } else {
663 assert_eq!(entry.validate(48), Ok(()));
664 }
665 }
666 }
667
668 #[test]
669 fn test_pasid_table_literal_words_and_bytes() {
670 let words = [0x9876_5432_128b, 0x0180_abcd, 0x60, 0, 0, 0, 0, 0];
671 let entry: PasidTableEntry = decode(&words);
672 assert!(entry.lo.p());
673 assert!(entry.lo.fpd());
674 assert_eq!(entry.lo.aw(), 2);
675 assert_eq!(entry.lo.pgtt(), 2);
676 assert!(entry.lo.ssade());
677 assert_eq!(entry.page_table_address(), 0x9876_5432_1000);
678 assert_eq!(entry.hi.did(), 0xabcd);
679 assert!(entry.hi.pwsnp());
680 assert!(entry.hi.pgsnp());
681 assert_eq!(entry.validate(48), Ok(()));
682 let built = PasidTableEntry {
683 lo: PasidTableEntryLo::new()
684 .with_p(true)
685 .with_fpd(true)
686 .with_aw(2)
687 .with_pgtt(2)
688 .with_ssade(true)
689 .with_ssptptr(0x9_8765_4321),
690 hi: PasidTableEntryHi::new()
691 .with_did(0xabcd)
692 .with_pwsnp(true)
693 .with_pgsnp(true),
694 first_stage: PasidTableEntryFirstStage::from(0x60),
695 reserved: [0; 5],
696 };
697 let expected: Vec<_> = words.iter().flat_map(|word| word.to_le_bytes()).collect();
698 assert_eq!(built.as_bytes(), expected);
699 assert_eq!(
700 PasidTableEntryLo::new().with_ssade(true).into_bits(),
701 1 << 9
702 );
703 assert_eq!(
704 PasidTableEntryHi::new().with_pwsnp(true).into_bits(),
705 1 << 23
706 );
707 assert_eq!(
708 PasidTableEntryHi::new().with_pgsnp(true).into_bits(),
709 1 << 24
710 );
711 }
712
713 #[test]
714 fn test_pasid_table_reserved_and_unsupported_bits() {
715 for bit in 0..512 {
716 let word = bit / 64;
717 let shift = bit % 64;
718 let is_reserved = match word {
719 0 => matches!(shift, 5 | 10..=11 | 48..=63),
720 1 => matches!(shift, 16..=22 | 27..=31),
721 2 => matches!(shift, 1 | 8..=11 | 48..=63),
722 _ => true,
723 };
724 let is_unsupported = match word {
725 1 => matches!(shift, 25..=26 | 32..=63),
726 2 => matches!(shift, 0 | 2..=4 | 7 | 12..=47),
727 _ => false,
728 };
729 if is_reserved || is_unsupported {
730 for pgtt in [2, 4] {
733 let mut words = [0; 8];
734 words[0] = 1 | (1 << 2) | (pgtt << 6);
735 words[word] |= 1 << shift;
736 let entry: PasidTableEntry = decode(&words);
737 let error = if is_reserved {
738 ScalableValidationError::ReservedBits {
739 word,
740 bits: 1 << shift,
741 }
742 } else {
743 ScalableValidationError::UnsupportedFields {
744 word,
745 bits: 1 << shift,
746 }
747 };
748 assert_eq!(entry.validate(48), Err(error), "bit {bit}, PGTT {pgtt}");
749 }
750 }
751 }
752 }
753
754 #[test]
755 fn test_pasid_ignored_fields_and_snoop_combinations() {
756 for ignored in [0, 1 << 5, 1 << 6, 3 << 5] {
757 for pgtt in [2, 4] {
758 for ssade in [0, 1] {
759 for pwsnp in [0, 1] {
760 for did in [0, 0xffff] {
762 let entry: PasidTableEntry = decode(&[
763 1 | (1 << 2) | (pgtt << 6) | (ssade << 9),
764 did | (pwsnp << 23) | (1 << 24),
765 ignored,
766 0,
767 0,
768 0,
769 0,
770 0,
771 ]);
772 assert_eq!(entry.validate(48), Ok(()));
773 }
774 }
775 }
776 }
777 }
778 let passthrough: PasidTableEntry = decode(&[0x0000_ffff_ffff_f31f, 0, 0x60, 0, 0, 0, 0, 0]);
780 assert_eq!(passthrough.lo.pgtt(), 4);
781 assert_eq!(passthrough.validate(48), Ok(()));
782 }
783
784 #[test]
785 fn test_pgtt_and_aw_encodings() {
786 assert_eq!(PasidTranslationType::FIRST_STAGE.0, 1);
787 assert_eq!(PasidTranslationType::SECOND_STAGE.0, 2);
788 assert_eq!(PasidTranslationType::NESTED.0, 3);
789 assert_eq!(PasidTranslationType::PASS_THROUGH.0, 4);
790 for pgtt in 0..=7u8 {
791 for aw in 0..=7u8 {
792 let entry: PasidTableEntry = decode(&[
793 1 | (u64::from(aw) << 2) | (u64::from(pgtt) << 6),
794 0,
795 0,
796 0,
797 0,
798 0,
799 0,
800 0,
801 ]);
802 assert_eq!(PasidTranslationType(entry.lo.pgtt()).0, pgtt);
803 let expected = match pgtt {
804 2 if aw == 1 || aw == 2 => Ok(()),
805 2 => Err(ScalableValidationError::UnsupportedAddressWidth(
806 AddressWidth(aw),
807 )),
808 4 => Ok(()),
809 _ => Err(ScalableValidationError::UnsupportedTranslationType(
810 PasidTranslationType(pgtt),
811 )),
812 };
813 assert_eq!(entry.validate(48), expected);
814 }
815 }
816 }
817
818 #[test]
819 fn test_absent_entries_ignore_all_but_fpd() {
820 for fpd in [false, true] {
821 let lo = !3u64 | ((fpd as u64) << 1);
822 let context: ScalableContextEntry = decode(&[lo, u64::MAX, u64::MAX, u64::MAX]);
823 let directory = PasidDirectoryEntry::from(lo);
824 let mut words = [u64::MAX; 8];
825 words[0] = lo;
826 let table: PasidTableEntry = decode(&words);
827 assert_eq!(
828 context.validate(48),
829 Err(ScalableValidationError::NotPresent)
830 );
831 assert_eq!(
832 directory.validate(48),
833 Err(ScalableValidationError::NotPresent)
834 );
835 assert_eq!(table.validate(48), Err(ScalableValidationError::NotPresent));
836 assert_eq!(context.lo.fpd(), fpd);
837 assert_eq!(directory.fpd(), fpd);
838 assert_eq!(table.lo.fpd(), fpd);
839 }
840 }
841
842 #[test]
843 fn test_host_address_width_boundaries() {
844 for haw in 0..=u8::MAX {
845 let root = ScalableRootEntryHalf::from(1);
846 let context: ScalableContextEntry = decode(&[1, 0, 0, 0]);
847 let directory = PasidDirectoryEntry::from(1);
848 let table: PasidTableEntry = decode(&[0x85, 0, 0, 0, 0, 0, 0, 0]);
849 let expected = if (12..=64).contains(&haw) {
850 Ok(())
851 } else {
852 Err(ScalableValidationError::InvalidHostAddressWidth(haw))
853 };
854 assert_eq!(root.validate(haw), expected);
855 assert_eq!(context.validate(haw), expected);
856 assert_eq!(directory.validate(haw), expected);
857 assert_eq!(table.validate(haw), expected);
858 }
859 for haw in [39, 48, 52, 63] {
860 let last_bit = 1u64 << (haw - 1);
861 let first_reserved = 1u64 << haw;
862 let valid = ScalableRootEntryHalf::from(1 | last_bit);
863 assert_eq!(valid.validate(haw), Ok(()));
864 let invalid = ScalableRootEntryHalf::from(1 | first_reserved);
865 assert_eq!(
866 invalid.validate(haw),
867 Err(ScalableValidationError::ReservedBits {
868 word: 0,
869 bits: first_reserved,
870 })
871 );
872 }
873 assert_eq!(
874 ScalableRootEntryHalf::from(1 | (1 << 63)).validate(64),
875 Ok(())
876 );
877 }
878}