1 // Copyright 2023, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Hardware management of the access flag and dirty state.
16 
17 use super::page_table::PageTable;
18 use super::util::flush_region;
19 use crate::{dsb, isb, read_sysreg, tlbi, write_sysreg};
20 use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion};
21 
22 /// Sets whether the hardware management of access and dirty state is enabled with
23 /// the given boolean.
set_dbm_enabled(enabled: bool)24 pub(super) fn set_dbm_enabled(enabled: bool) {
25     if !dbm_available() {
26         return;
27     }
28     // TCR_EL1.{HA,HD} bits controlling hardware management of access and dirty state
29     const TCR_EL1_HA_HD_BITS: usize = 3 << 39;
30 
31     let mut tcr = read_sysreg!("tcr_el1");
32     if enabled {
33         tcr |= TCR_EL1_HA_HD_BITS
34     } else {
35         tcr &= !TCR_EL1_HA_HD_BITS
36     };
37     // SAFETY: Changing this bit in TCR doesn't affect Rust's view of memory.
38     unsafe { write_sysreg!("tcr_el1", tcr) }
39     isb!();
40 }
41 
42 /// Returns `true` if hardware dirty state management is available.
dbm_available() -> bool43 fn dbm_available() -> bool {
44     if !cfg!(feature = "cpu_feat_hafdbs") {
45         return false;
46     }
47     // Hardware dirty bit management available flag (ID_AA64MMFR1_EL1.HAFDBS[1])
48     const DBM_AVAILABLE: usize = 1 << 1;
49     read_sysreg!("id_aa64mmfr1_el1") & DBM_AVAILABLE != 0
50 }
51 
52 /// Flushes a memory range the descriptor refers to, if the descriptor is in writable-dirty state.
flush_dirty_range( va_range: &MemoryRegion, desc: &Descriptor, _level: usize, ) -> Result<(), ()>53 pub(super) fn flush_dirty_range(
54     va_range: &MemoryRegion,
55     desc: &Descriptor,
56     _level: usize,
57 ) -> Result<(), ()> {
58     let flags = desc.flags().ok_or(())?;
59     if !flags.contains(Attributes::READ_ONLY) {
60         flush_region(va_range.start().0, va_range.len());
61     }
62     Ok(())
63 }
64 
65 /// Clears read-only flag on a PTE, making it writable-dirty. Used when dirty state is managed
66 /// in software to handle permission faults on read-only descriptors.
mark_dirty_block( va_range: &MemoryRegion, desc: &mut Descriptor, _level: usize, ) -> Result<(), ()>67 pub(super) fn mark_dirty_block(
68     va_range: &MemoryRegion,
69     desc: &mut Descriptor,
70     _level: usize,
71 ) -> Result<(), ()> {
72     let flags = desc.flags().ok_or(())?;
73     if flags.contains(Attributes::DBM) {
74         assert!(flags.contains(Attributes::READ_ONLY), "unexpected PTE writable state");
75         desc.modify_flags(Attributes::empty(), Attributes::READ_ONLY);
76         // Updating the read-only bit of a PTE requires TLB invalidation.
77         // A TLB maintenance instruction is only guaranteed to be complete after a DSB instruction.
78         // An ISB instruction is required to ensure the effects of completed TLB maintenance
79         // instructions are visible to instructions fetched afterwards.
80         // See ARM ARM E2.3.10, and G5.9.
81         tlbi!("vale1", PageTable::ASID, va_range.start().0);
82         dsb!("ish");
83         isb!();
84         Ok(())
85     } else {
86         Err(())
87     }
88 }
89