use core::arch::asm;
use aarch64_cpu::{asm::barrier, registers::*};
use ax_memory_addr::{PhysAddr, VirtAddr};
#[cfg(not(feature = "arm-el2"))]
use super::asid::configured_tag_capacity;
#[cfg(feature = "tls")]
use crate::KernelTlsBase;
#[cfg(feature = "uspace")]
use crate::{InstalledAddressSpace, InstalledAddressSpaceMode};
pub fn address_space_tag_capacity(_cpu_count: usize) -> u32 {
#[cfg(feature = "arm-el2")]
{
1
}
#[cfg(not(feature = "arm-el2"))]
{
configured_tag_capacity(
ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::ASIDBits),
TCR_EL1.read(TCR_EL1::AS),
)
}
}
#[cfg(all(feature = "uspace", not(feature = "arm-el2")))]
fn flush_tlb_asid(asid: u16) {
let operand = u64::from(asid) << 48;
unsafe {
asm!(
"dsb ishst; tlbi aside1is, {operand}; dsb ish; isb",
operand = in(reg) operand,
)
}
}
#[cfg(feature = "uspace")]
pub unsafe fn install_user_address_space(address_space: InstalledAddressSpace) {
address_space.validate_architecture_support();
#[cfg(not(feature = "arm-el2"))]
if matches!(address_space.mode(), InstalledAddressSpaceMode::Tagged) {
let capacity = address_space_tag_capacity(1);
if u32::from(address_space.hardware_tag()) < capacity {
flush_tlb_asid(address_space.hardware_tag());
let value = address_space.root().as_usize() as u64
| (u64::from(address_space.hardware_tag()) << 48);
TTBR0_EL1.set(value);
barrier::isb(barrier::SY);
return;
}
}
TTBR0_EL1.set(address_space.root().as_usize() as u64);
flush_tlb(None);
}
#[inline]
pub fn enable_irqs() {
unsafe { asm!("msr daifclr, #2") };
}
#[inline]
pub fn disable_irqs() {
unsafe { asm!("msr daifset, #2") };
}
#[inline]
pub fn irqs_enabled() -> bool {
!DAIF.matches_all(DAIF::I::Masked)
}
#[inline]
pub fn wait_for_irqs() {
aarch64_cpu::asm::wfi();
}
#[inline]
pub fn wait_for_irqs_disabled() {
debug_assert!(!irqs_enabled());
barrier::dsb(barrier::SY);
aarch64_cpu::asm::wfi();
enable_irqs();
}
#[inline]
pub fn halt() {
disable_irqs();
aarch64_cpu::asm::wfi(); }
#[inline]
pub fn read_kernel_page_table() -> PhysAddr {
#[cfg(not(feature = "arm-el2"))]
let root = TTBR1_EL1.get();
#[cfg(feature = "arm-el2")]
let root = TTBR0_EL2.get();
pa!(root as usize)
}
#[inline]
pub fn read_user_page_table() -> PhysAddr {
const TTBR_BADDR_MASK: u64 = (1 << 48) - 1;
let root = TTBR0_EL1.get() & TTBR_BADDR_MASK;
pa!(root as usize)
}
#[inline]
pub unsafe fn write_kernel_page_table(root_paddr: PhysAddr) {
#[cfg(not(feature = "arm-el2"))]
{
TTBR1_EL1.set(root_paddr.as_usize() as _);
}
#[cfg(feature = "arm-el2")]
{
TTBR0_EL2.set(root_paddr.as_usize() as _);
}
}
#[inline]
pub unsafe fn write_user_page_table(root_paddr: PhysAddr) {
TTBR0_EL1.set(root_paddr.as_usize() as _);
}
#[inline]
pub fn synchronize_page_table_writes() {
unsafe { asm!("dsb ishst") };
}
#[inline]
pub fn flush_tlb(vaddr: Option<VirtAddr>) {
if let Some(vaddr) = vaddr {
const VA_MASK: usize = (1 << 44) - 1; let operand = (vaddr.as_usize() >> 12) & VA_MASK;
#[cfg(not(feature = "arm-el2"))]
unsafe {
asm!("dsb nshst; tlbi vaae1, {}; dsb nsh; isb", in(reg) operand)
}
#[cfg(feature = "arm-el2")]
unsafe {
asm!("dsb nshst; tlbi vae2, {}; dsb nsh; isb", in(reg) operand)
}
} else {
#[cfg(not(feature = "arm-el2"))]
unsafe {
asm!("dsb nshst; tlbi vmalle1; dsb nsh; isb")
}
#[cfg(feature = "arm-el2")]
unsafe {
asm!("dsb nshst; tlbi alle2; dsb nsh; isb")
}
}
}
#[inline]
pub fn update_mmu_cache(_vaddr: VirtAddr) {}
#[inline]
pub fn flush_icache_all() {
unsafe { asm!("ic iallu; dsb sy; isb") };
}
#[inline]
fn read_ctr_el0() -> u64 {
let value;
unsafe {
asm!("mrs {}, ctr_el0", out(reg) value);
}
value
}
#[inline]
pub fn dcache_line_size_from_ctr() -> usize {
let ctr = read_ctr_el0();
let dminline = ((ctr >> 16) & 0xf) as usize;
4usize << dminline
}
#[inline]
pub fn icache_line_size_from_ctr() -> usize {
let ctr = read_ctr_el0();
let iminline = (ctr & 0xf) as usize;
4usize << iminline
}
#[inline]
pub fn clean_dcache_range_to_pou(vaddr: VirtAddr, size: usize) {
if size == 0 {
return;
}
let line_size = dcache_line_size_from_ctr();
let start = vaddr.as_usize() & !(line_size - 1);
let end = (vaddr.as_usize() + size + line_size - 1) & !(line_size - 1);
for line in (start..end).step_by(line_size) {
unsafe { asm!("dc cvau, {0:x}", in(reg) line) };
}
unsafe { asm!("dsb sy") };
}
#[inline]
pub fn flush_dcache_line(vaddr: VirtAddr) {
unsafe { asm!("dc ivac, {0:x}; dsb sy; isb", in(reg) vaddr.as_usize()) };
}
#[inline]
pub unsafe fn write_exception_vector_base(vbar: usize) {
#[cfg(not(feature = "arm-el2"))]
VBAR_EL1.set(vbar as _);
#[cfg(feature = "arm-el2")]
VBAR_EL2.set(vbar as _);
}
#[inline]
#[cfg(feature = "tls")]
pub fn read_thread_pointer() -> KernelTlsBase {
KernelTlsBase::new(TPIDR_EL0.get() as usize)
}
#[inline]
#[cfg(feature = "tls")]
pub unsafe fn write_thread_pointer(kernel_tls: KernelTlsBase) {
TPIDR_EL0.set(kernel_tls.as_usize() as _)
}
#[inline]
pub fn enable_fp() {
CPACR_EL1.write(CPACR_EL1::FPEN::TrapNothing);
barrier::isb(barrier::SY);
}
#[cfg(feature = "uspace")]
core::arch::global_asm!(include_str!("user_copy.S"), include_str!("user_atomic.S"),);
#[cfg(feature = "uspace")]
unsafe extern "C" {
pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize;
}
#[cfg(all(feature = "uspace", not(feature = "arm-el2")))]
#[inline]
pub unsafe fn user_access_ok_page(vaddr: usize, access: crate::UserAccessType) -> bool {
let par: u64;
unsafe {
if access == crate::UserAccessType::Write {
asm!(
"at s1e0w, {vaddr}",
"isb",
"mrs {par}, par_el1",
vaddr = in(reg) vaddr,
par = out(reg) par,
options(nostack, preserves_flags),
);
} else {
asm!(
"at s1e0r, {vaddr}",
"isb",
"mrs {par}, par_el1",
vaddr = in(reg) vaddr,
par = out(reg) par,
options(nostack, preserves_flags),
);
}
}
par & 1 == 0
}
#[cfg(all(feature = "uspace", feature = "arm-el2"))]
#[inline]
pub unsafe fn user_access_ok_page(_vaddr: usize, _access: crate::UserAccessType) -> bool {
false
}