aarch64_cpu_ext/structures/
tte.rs

1use core::marker::PhantomData;
2
3/// This module defines the Translation Table Entry (TTE) structure used in AArch64 architecture.
4use tock_registers::{LocalRegisterCopy, register_bitfields};
5
6pub trait Granule: Clone + Copy {
7    const M: u32;
8    const SIZE: usize = 2usize.pow(Self::M);
9    const MASK: u64 = (1u64 << Self::M) - 1; // Mask for alignment
10}
11
12#[derive(Clone, Copy)]
13pub struct Granule4KB {}
14
15impl Granule for Granule4KB {
16    const M: u32 = 12; // log2(4096) = 12
17}
18
19#[derive(Clone, Copy)]
20pub struct Granule16KB {}
21
22impl Granule for Granule16KB {
23    const M: u32 = 14; // log2(16384) = 14
24}
25
26#[derive(Clone, Copy)]
27pub struct Granule64KB {}
28
29impl Granule for Granule64KB {
30    const M: u32 = 16; // log2(65536) = 16
31}
32
33pub trait OA: Clone + Copy {
34    const BITS: usize;
35}
36
37#[derive(Clone, Copy)]
38pub struct OA48 {}
39
40impl OA for OA48 {
41    const BITS: usize = 48; // 48-bit output address
42}
43
44#[derive(Clone, Copy)]
45pub struct OA52 {}
46
47impl OA for OA52 {
48    const BITS: usize = 52; // 52-bit output address
49}
50
51/// Access permissions for Stage 1 translation using Direct permissions
52/// Based on ARM DDI 0487K.a Table D8-49
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum AccessPermission {
55    /// Read/write access for privileged level only, no access for unprivileged
56    /// AP[2:1] = 0b00 (when supporting two privilege levels)
57    /// For single privilege level: Read/write access
58    PrivilegedReadWrite = 0b00,
59
60    /// Read/write access for both privileged and unprivileged levels
61    /// AP[2:1] = 0b01
62    ReadWrite = 0b01,
63
64    /// Read-only access for privileged level only, no access for unprivileged
65    /// AP[2:1] = 0b10 (when supporting two privilege levels)
66    /// For single privilege level: Read-only access
67    PrivilegedReadOnly = 0b10,
68
69    /// Read-only access for both privileged and unprivileged levels
70    /// AP[2:1] = 0b11
71    ReadOnly = 0b11,
72}
73
74impl AccessPermission {
75    /// Get the AP field value for the TTE
76    pub const fn as_bits(self) -> u8 {
77        self as u8
78    }
79
80    /// Create from AP bits
81    pub const fn from_bits(bits: u8) -> Option<Self> {
82        match bits & 0b11 {
83            0b00 => Some(Self::PrivilegedReadWrite),
84            0b01 => Some(Self::ReadWrite),
85            0b10 => Some(Self::PrivilegedReadOnly),
86            0b11 => Some(Self::ReadOnly),
87            _ => None,
88        }
89    }
90
91    /// Check if this permission allows unprivileged access
92    pub const fn allows_unprivileged(self) -> bool {
93        matches!(self, Self::ReadWrite | Self::ReadOnly)
94    }
95
96    /// Check if this permission allows write access at the privileged level
97    pub const fn allows_privileged_write(self) -> bool {
98        matches!(self, Self::PrivilegedReadWrite | Self::ReadWrite)
99    }
100
101    /// Check if this permission allows write access at the unprivileged level
102    pub const fn allows_unprivileged_write(self) -> bool {
103        matches!(self, Self::ReadWrite)
104    }
105}
106
107/// Shareability
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub enum Shareability {
110    NonShareable,
111    OuterShareable,
112    InnerShareable,
113}
114
115register_bitfields![u64,
116    /// Translation Table Entry for AArch64
117    /// Based on ARMv8-A Architecture Reference Manual
118    TTE64_REG [
119        /// Valid bit - indicates if this entry is valid
120        VALID OFFSET(0) NUMBITS(1) [
121            Invalid = 0,
122            Valid = 1
123        ],
124
125        /// Type bit for level 0, 1, and 2 entries
126        /// Combined with VALID bit determines the entry type
127        TYPE OFFSET(1) NUMBITS(1) [
128            Block = 0,  // Block entry (when VALID=1)
129            Table = 1   // Table entry (when VALID=1)
130        ],
131
132        /// Memory attributes index for MAIR_ELx
133        ATTR_INDX OFFSET(2) NUMBITS(3) [],
134
135        /// Non-secure bit
136        NS OFFSET(5) NUMBITS(1) [
137            Secure = 0,
138            NonSecure = 1
139        ],
140
141        /// Access permission bits
142        /// AP[2:1] for Stage 1 translation using Direct permissions
143        /// Based on ARM DDI 0487K.a Table D8-49
144        AP OFFSET(6) NUMBITS(2) [
145            PrivilegedReadWrite = 0b00,  // Read/write for privileged level only
146            ReadWrite = 0b01,            // Read/write for both privileged and unprivileged
147            PrivilegedReadOnly = 0b10,   // Read-only for privileged level only
148            ReadOnly = 0b11              // Read-only for both privileged and unprivileged
149        ],
150
151        /// Shareability field
152        SH OFFSET(8) NUMBITS(2) [
153            NonShareable = 0b00,
154            OuterShareable = 0b10,
155            InnerShareable = 0b11
156        ],
157
158        /// Access flag
159        AF OFFSET(10) NUMBITS(1) [
160            NotAccessed = 0,
161            Accessed = 1
162        ],
163
164        /// Not global bit
165        NG OFFSET(11) NUMBITS(1) [
166            Global = 0,
167            NotGlobal = 1
168        ],
169
170        ADDR OFFSET(12) NUMBITS(38) [],
171
172        /// Dirty bit modifier (ARMv8.1+)
173        DBM OFFSET(51) NUMBITS(1) [
174            ReadOnly = 0,
175            Writable = 1
176        ],
177
178        /// Contiguous bit
179        CONTIG OFFSET(52) NUMBITS(1) [
180            NotContiguous = 0,
181            Contiguous = 1
182        ],
183
184        /// Privileged execute-never
185        PXN OFFSET(53) NUMBITS(1) [
186            ExecuteAllowed = 0,
187            ExecuteNever = 1
188        ],
189
190        /// Execute-never or Unprivileged execute-never
191        XN_UXN OFFSET(54) NUMBITS(1) [
192            ExecuteAllowed = 0,
193            ExecuteNever = 1
194        ],
195
196        /// Reserved for software use (bits 58:55)
197        SW_RESERVED OFFSET(55) NUMBITS(4) []
198    ]
199];
200
201#[derive(Clone, Copy)]
202pub struct TTE64<G: Granule, O: OA> {
203    reg: LocalRegisterCopy<u64, TTE64_REG::Register>,
204    _marker: PhantomData<(G, O)>,
205}
206
207impl<G: Granule, O: OA> TTE64<G, O> {
208    /// Create a new TTE64 from a raw u64 value
209    pub const fn new(value: u64) -> Self {
210        Self {
211            reg: LocalRegisterCopy::new(value),
212            _marker: PhantomData,
213        }
214    }
215
216    /// Create an invalid TTE (all zeros)
217    pub const fn invalid() -> Self {
218        Self::new(0)
219    }
220
221    /// Create a table entry with more convenient parameters
222    pub fn new_table(table_addr: u64) -> Self {
223        let mut tte = Self::new(0);
224
225        tte.reg
226            .modify(TTE64_REG::VALID::Valid + TTE64_REG::TYPE::Table + TTE64_REG::AF::Accessed);
227        tte.set_address(table_addr);
228        tte
229    }
230
231    /// Create a block entry with BlockConfig
232    pub fn new_block(block_addr: u64) -> Self {
233        let mut tte = Self::new(0);
234
235        tte.reg
236            .modify(TTE64_REG::VALID::Valid + TTE64_REG::TYPE::Block + TTE64_REG::AF::Accessed);
237        tte.set_address(block_addr);
238        tte
239    }
240
241    /// Get the raw u64 value
242    pub fn get(&self) -> u64 {
243        self.reg.get()
244    }
245
246    /// Check if this TTE is valid
247    pub fn is_valid(&self) -> bool {
248        self.reg.is_set(TTE64_REG::VALID)
249    }
250
251    pub fn set_is_valid(&mut self, val: bool) {
252        if val {
253            self.reg.modify(TTE64_REG::VALID::Valid);
254        } else {
255            self.reg.modify(TTE64_REG::VALID::Invalid);
256        }
257    }
258
259    /// Check if this TTE is a table entry (vs block entry)
260    pub fn is_table(&self) -> bool {
261        self.is_valid() && self.reg.is_set(TTE64_REG::TYPE)
262    }
263
264    /// Check if this TTE is a block entry (vs table entry)
265    pub fn is_block(&self) -> bool {
266        self.is_valid() && !self.reg.is_set(TTE64_REG::TYPE)
267    }
268
269    pub fn set_address(&mut self, addr: u64) {
270        assert!(
271            addr & G::MASK == 0,
272            "Address must be aligned to granule size"
273        );
274        assert!(
275            addr < (1u64 << O::BITS),
276            "Address exceeds output address width"
277        );
278        let val = addr >> TTE64_REG::ADDR.shift; // Shift to align with TTE address bits
279        self.reg.modify(TTE64_REG::ADDR.val(val));
280    }
281
282    /// Get the output address (physical address) from this TTE
283    /// This extracts the address bits and reconstructs the physical address
284    pub fn address(&self) -> u64 {
285        if !self.is_valid() {
286            return 0;
287        }
288
289        let raw_value = self.reg.get();
290        let m = G::M; // granule size log2 (12, 14, or 16)
291
292        let bit_start = m;
293        let bit_end =
294
295        // Handle 52-bit output address extension
296        if O::BITS == 52 && (G::M == 12 || G::M == 14) {
297            50
298        } else {
299            48
300        };
301        let mask = ((1u64 << (bit_end - bit_start + 1)) - 1) << bit_start;
302        raw_value & mask
303    }
304
305    pub fn address_with_page_level(&self, level: usize) -> u64 {
306        if self.is_table() {
307            return self.address();
308        }
309        let raw_addr = self.reg.get();
310        let n = match (G::M, level) {
311            (12, 0) => 39,
312            (12, 1) => 30,
313            (12, 2) => 21,
314            (14, 1) => 36,
315            (14, 2) => 25,
316            (16, 1) => 42,
317            (16, 2) => 29,
318            _ => panic!("Invalid granule size or level combination"),
319        };
320
321        let bit_start = n;
322        // 4KB and 16KB granules, 52-bit OA
323        let bit_end = if O::BITS == 52 && (G::M == 12 || G::M == 14) {
324            50
325        } else {
326            48
327        };
328        let mask = ((1u64 << (bit_end - bit_start + 1)) - 1) << bit_start;
329        raw_addr & mask
330    }
331
332    /// Check if this TTE has the access flag set
333    pub fn is_accessed(&self) -> bool {
334        self.reg.is_set(TTE64_REG::AF)
335    }
336
337    /// Get the memory attribute index
338    pub fn attr_index(&self) -> u64 {
339        self.reg.read(TTE64_REG::ATTR_INDX)
340    }
341
342    /// Check if this TTE allows execution
343    pub fn is_executable(&self) -> bool {
344        !self.reg.is_set(TTE64_REG::XN_UXN)
345    }
346
347    /// Check if this TTE allows privileged execution
348    pub fn is_privileged_executable(&self) -> bool {
349        !self.reg.is_set(TTE64_REG::PXN)
350    }
351
352    /// Get access permissions
353    pub fn access_permission(&self) -> AccessPermission {
354        AccessPermission::from_bits(self.reg.read(TTE64_REG::AP) as _).unwrap()
355    }
356
357    /// Get shareability attributes
358    pub fn shareability(&self) -> Shareability {
359        match self.reg.read_as_enum(TTE64_REG::SH) {
360            Some(TTE64_REG::SH::Value::NonShareable) => Shareability::NonShareable,
361            Some(TTE64_REG::SH::Value::OuterShareable) => Shareability::OuterShareable,
362            Some(TTE64_REG::SH::Value::InnerShareable) => Shareability::InnerShareable,
363            None => unreachable!("invalid value"),
364        }
365    }
366
367    pub fn set_shareability(&mut self, shareability: Shareability) {
368        self.reg.modify(match shareability {
369            Shareability::NonShareable => TTE64_REG::SH::NonShareable,
370            Shareability::OuterShareable => TTE64_REG::SH::OuterShareable,
371            Shareability::InnerShareable => TTE64_REG::SH::InnerShareable,
372        });
373    }
374
375    /// Set the access flag
376    pub fn set_access(&mut self) {
377        self.reg.modify(TTE64_REG::AF::Accessed);
378    }
379
380    /// Clear the access flag
381    pub fn clear_access(&mut self) {
382        self.reg.modify(TTE64_REG::AF::NotAccessed);
383    }
384
385    /// Check if the contiguous bit is set
386    pub fn is_contiguous(&self) -> bool {
387        self.reg.is_set(TTE64_REG::CONTIG)
388    }
389
390    /// Set the contiguous bit
391    pub fn set_contiguous(&mut self) {
392        self.reg.modify(TTE64_REG::CONTIG::Contiguous);
393    }
394
395    /// Check if this is a global mapping
396    pub fn is_global(&self) -> bool {
397        !self.reg.is_set(TTE64_REG::NG)
398    }
399
400    /// Set the not-global bit (make it process-specific)
401    pub fn set_not_global(&mut self) {
402        self.reg.modify(TTE64_REG::NG::NotGlobal);
403    }
404
405    /// Check if dirty bit modifier is set (ARMv8.1+)
406    pub fn is_dirty_writable(&self) -> bool {
407        self.reg.is_set(TTE64_REG::DBM)
408    }
409
410    /// Get the software reserved bits
411    pub fn sw_reserved(&self) -> u64 {
412        self.reg.read(TTE64_REG::SW_RESERVED)
413    }
414
415    /// Set the software reserved bits
416    pub fn set_sw_reserved(&mut self, value: u64) {
417        self.reg.modify(TTE64_REG::SW_RESERVED.val(value & 0xF));
418    }
419}
420
421// Convenient type aliases for common configurations
422/// TTE with 4KB granule and 48-bit output addresses
423pub type TTE4K48 = TTE64<Granule4KB, OA48>;
424
425/// TTE with 4KB granule and 52-bit output addresses
426pub type TTE4K52 = TTE64<Granule4KB, OA52>;
427
428/// TTE with 16KB granule and 48-bit output addresses
429pub type TTE16K48 = TTE64<Granule16KB, OA48>;
430
431/// TTE with 16KB granule and 52-bit output addresses
432pub type TTE16K52 = TTE64<Granule16KB, OA52>;
433
434/// TTE with 64KB granule and 48-bit output addresses
435pub type TTE64K48 = TTE64<Granule64KB, OA48>;
436
437/// TTE with 64KB granule and 52-bit output addresses
438pub type TTE64K52 = TTE64<Granule64KB, OA52>;
439
440/// Constants for different granule sizes block sizes at different levels
441pub mod block_sizes {
442    /// Block sizes for 4KB granule
443    pub mod granule_4k {
444        pub const LEVEL1_BLOCK_SIZE: usize = 1024 * 1024 * 1024; // 1GB
445        pub const LEVEL2_BLOCK_SIZE: usize = 2 * 1024 * 1024; // 2MB
446        pub const LEVEL3_PAGE_SIZE: usize = 4 * 1024; // 4KB
447    }
448
449    /// Block sizes for 16KB granule
450    pub mod granule_16k {
451        pub const LEVEL1_BLOCK_SIZE: usize = 64 * 1024 * 1024 * 1024; // 64GB
452        pub const LEVEL2_BLOCK_SIZE: usize = 32 * 1024 * 1024; // 32MB
453        pub const LEVEL3_PAGE_SIZE: usize = 16 * 1024; // 16KB
454    }
455
456    /// Block sizes for 64KB granule
457    pub mod granule_64k {
458        pub const LEVEL1_BLOCK_SIZE: usize = 4 * 1024 * 1024 * 1024; // 4TB (level0)
459        pub const LEVEL2_BLOCK_SIZE: usize = 512 * 1024 * 1024; // 512MB
460        pub const LEVEL3_PAGE_SIZE: usize = 64 * 1024; // 64KB
461    }
462}
463
464/// Helper functions for address calculations
465impl<G: Granule, O: OA> TTE64<G, O> {
466    /// Calculate the index for a virtual address at a given level
467    pub fn calculate_index(va: u64, level: usize) -> usize {
468        match (G::M, level) {
469            // 4KB granule
470            (12, 0) => ((va >> 39) & 0x1FF) as usize, // 9 bits
471            (12, 1) => ((va >> 30) & 0x1FF) as usize, // 9 bits
472            (12, 2) => ((va >> 21) & 0x1FF) as usize, // 9 bits
473            (12, 3) => ((va >> 12) & 0x1FF) as usize, // 9 bits
474            // 16KB granule
475            (14, 0) => ((va >> 47) & 0x1) as usize,   // 1 bit
476            (14, 1) => ((va >> 36) & 0x7FF) as usize, // 11 bits
477            (14, 2) => ((va >> 25) & 0x7FF) as usize, // 11 bits
478            (14, 3) => ((va >> 14) & 0x7FF) as usize, // 11 bits
479            // 64KB granule
480            (16, 1) => ((va >> 42) & 0x3F) as usize, // 6 bits
481            (16, 2) => ((va >> 29) & 0x1FFF) as usize, // 13 bits
482            (16, 3) => ((va >> 16) & 0x1FFF) as usize, // 13 bits
483            _ => panic!("Invalid granule size or level combination"),
484        }
485    }
486
487    /// Check if an address is aligned to the granule boundary
488    pub fn is_aligned(addr: u64) -> bool {
489        (addr & G::MASK) == 0
490    }
491
492    /// Align an address down to the granule boundary
493    pub fn align_down(addr: u64) -> u64 {
494        addr & !G::MASK
495    }
496
497    /// Align an address up to the granule boundary
498    pub fn align_up(addr: u64) -> u64 {
499        (addr + G::MASK) & !G::MASK
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    #[test]
508    fn test_address_extraction_4k_48bit() {
509        // Test 4KB granule with 48-bit output address
510        type TTE = TTE64<Granule4KB, OA48>;
511
512        // Test table descriptor
513        let table_addr = 0x1000_0000_1000; // 48-bit address aligned to 4KB
514        let tte_table = TTE::new_table(table_addr);
515        assert_eq!(tte_table.address(), table_addr);
516
517        // Test block descriptor
518        let block = 2 * 1024 * 1024; // 2MB
519        let block_addr = 0x2000_0000_1000 + block; // 48-bit address aligned to 4KB 
520        let tte_block = TTE::new_block(block_addr);
521        assert_eq!(
522            tte_block.address_with_page_level(2),
523            0x2000_0000_0000 + block
524        );
525    }
526
527    #[test]
528    fn test_address_extraction_4k_52bit() {
529        // Test 4KB granule with 52-bit output address
530        type TTE = TTE64<Granule4KB, OA52>;
531
532        let table_addr = (1 << 50) - 0x1000; // 52-bit address with high bits
533        let tte_table = TTE::new_table(table_addr);
534        let read_addr = tte_table.address();
535        assert_eq!(
536            read_addr, table_addr,
537            "want {:#x} != read {:#x} address mismatch",
538            table_addr, read_addr
539        );
540    }
541
542    #[test]
543    fn test_address_extraction_16k_48bit() {
544        // Test 16KB granule with 48-bit output address
545        type TTE = TTE64<Granule16KB, OA48>;
546
547        // Test table descriptor - must be aligned to 16KB boundary
548        let table_addr = (1 << 47) + 16 * 1024; // 48-bit address aligned to 16KB
549        let tte_table = TTE::new_table(table_addr);
550        let read = tte_table.address();
551        assert_eq!(
552            table_addr, read,
553            "want {:#x} != read {:#x} address mismatch",
554            table_addr, read
555        );
556
557        // Test block descriptor
558        let block_addr = 0x2000_0000_0000; // 48-bit address aligned to 16KB
559        let tte_block = TTE::new_block(block_addr);
560        assert_eq!(tte_block.address(), block_addr);
561    }
562
563    #[test]
564    fn test_address_extraction_16k_52bit() {
565        // Test 16KB granule with 52-bit output address
566        type TTE = TTE64<Granule16KB, OA52>;
567
568        // Test with high address bits
569        let table_addr = (1 << 50) - 0x4000; // 52-bit address with high bits, aligned to 16KB
570        let tte_table = TTE::new_table(table_addr); // Base address aligned to 16KB
571
572        assert_eq!(tte_table.address(), table_addr);
573    }
574
575    #[test]
576    fn test_address_extraction_64k_48bit() {
577        // Test 64KB granule with 48-bit output address
578        type TTE = TTE64<Granule64KB, OA48>;
579
580        // Test table descriptor - must be aligned to 64KB boundary
581        let table_addr = 0x1000_0001_0000; // 48-bit address aligned to 64KB
582        let tte_table = TTE::new_table(table_addr);
583        assert_eq!(tte_table.address(), table_addr);
584
585        // Test block descriptor
586        let block_addr = 0x2000_0002_0000; // 48-bit address aligned to 64KB
587        let tte_block = TTE::new_block(block_addr);
588        assert_eq!(tte_block.address(), block_addr);
589    }
590
591    #[test]
592    fn test_address_extraction_64k_52bit() {
593        // Test 64KB granule with 52-bit output address
594        type TTE = TTE64<Granule64KB, OA52>;
595
596        let table_addr = 0xf00_1001_0000u64; // 52-bit address with high bits, aligned to 64KB
597        let tte_table = TTE::new_table(table_addr); // Base address aligned to 64KB
598
599        assert_eq!(
600            table_addr,
601            tte_table.address(),
602            "want {:#x} != read {:#x} address mismatch",
603            table_addr,
604            tte_table.address()
605        );
606    }
607
608    #[test]
609    fn test_invalid_tte_address() {
610        // Test that invalid TTEs return 0 address
611        type TTE = TTE64<Granule4KB, OA48>;
612
613        let tte_invalid = TTE::invalid();
614        assert_eq!(tte_invalid.address(), 0);
615        assert!(!tte_invalid.is_valid());
616    }
617
618    #[test]
619    fn test_granule_constants() {
620        // Test that granule constants match expected values for m calculation
621        assert_eq!(Granule4KB::M, 12); // log2(4096) = 12
622        assert_eq!(Granule16KB::M, 14); // log2(16384) = 14  
623        assert_eq!(Granule64KB::M, 16); // log2(65536) = 16
624
625        // Test that granule sizes are correct
626        assert_eq!(Granule4KB::SIZE, 4096);
627        assert_eq!(Granule16KB::SIZE, 16384);
628        assert_eq!(Granule64KB::SIZE, 65536);
629
630        // Test that masks are correct for alignment
631        assert_eq!(Granule4KB::MASK, 0xFFF);
632        assert_eq!(Granule16KB::MASK, 0x3FFF);
633        assert_eq!(Granule64KB::MASK, 0xFFFF);
634    }
635}