blob: 401022e35e363eb8603965fdbd6745f29878bacf [file] [log] [blame]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Hardware management of the access flag and dirty state.
use super::page_table::{is_leaf_pte, PageTable};
use super::util::flush_region;
use crate::{dsb, isb, read_sysreg, tlbi, write_sysreg};
use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion};
/// Sets whether the hardware management of access and dirty state is enabled with
/// the given boolean.
pub(super) fn set_dbm_enabled(enabled: bool) {
if !dbm_available() {
return;
}
// TCR_EL1.{HA,HD} bits controlling hardware management of access and dirty state
const TCR_EL1_HA_HD_BITS: usize = 3 << 39;
let mut tcr = read_sysreg!("tcr_el1");
if enabled {
tcr |= TCR_EL1_HA_HD_BITS
} else {
tcr &= !TCR_EL1_HA_HD_BITS
};
// SAFETY: Changing this bit in TCR doesn't affect Rust's view of memory.
unsafe { write_sysreg!("tcr_el1", tcr) }
isb!();
}
/// Returns `true` if hardware dirty state management is available.
fn dbm_available() -> bool {
if !cfg!(feature = "cpu_feat_hafdbs") {
return false;
}
// Hardware dirty bit management available flag (ID_AA64MMFR1_EL1.HAFDBS[1])
const DBM_AVAILABLE: usize = 1 << 1;
read_sysreg!("id_aa64mmfr1_el1") & DBM_AVAILABLE != 0
}
/// Flushes a memory range the descriptor refers to, if the descriptor is in writable-dirty state.
pub(super) fn flush_dirty_range(
va_range: &MemoryRegion,
desc: &mut Descriptor,
level: usize,
) -> Result<(), ()> {
// Only flush ranges corresponding to dirty leaf PTEs.
let flags = desc.flags().ok_or(())?;
if !is_leaf_pte(&flags, level) {
return Ok(());
}
if !flags.contains(Attributes::READ_ONLY) {
flush_region(va_range.start().0, va_range.len());
}
Ok(())
}
/// Clears read-only flag on a PTE, making it writable-dirty. Used when dirty state is managed
/// in software to handle permission faults on read-only descriptors.
pub(super) fn mark_dirty_block(
va_range: &MemoryRegion,
desc: &mut Descriptor,
level: usize,
) -> Result<(), ()> {
let flags = desc.flags().ok_or(())?;
if !is_leaf_pte(&flags, level) {
return Ok(());
}
if flags.contains(Attributes::DBM) {
assert!(flags.contains(Attributes::READ_ONLY), "unexpected PTE writable state");
desc.modify_flags(Attributes::empty(), Attributes::READ_ONLY);
// Updating the read-only bit of a PTE requires TLB invalidation.
// A TLB maintenance instruction is only guaranteed to be complete after a DSB instruction.
// An ISB instruction is required to ensure the effects of completed TLB maintenance
// instructions are visible to instructions fetched afterwards.
// See ARM ARM E2.3.10, and G5.9.
tlbi!("vale1", PageTable::ASID, va_range.start().0);
dsb!("ish");
isb!();
Ok(())
} else {
Err(())
}
}