use crate::{error::EfiError, pi::protocols::cpu_arch::CpuFlushType};
use core::num::NonZeroU64;
use r_efi::efi;
pub(super) struct AArch64;
impl super::ArchSupport for AArch64 {}
const DAIF_WR_IRQ_BIT: u64 = 0x2;
#[macro_export]
macro_rules! read_sysreg {
($name:ident) => {
{
let mut value: u64;
unsafe {
core::arch::asm!(
concat!("mrs {value:x}, ", core::stringify!($name)),
value = out(reg) value,
options(nomem, nostack),
);
}
value
}
}
}
#[macro_export]
macro_rules! write_sysreg {
(reg $dest:ident, imm $imm:expr) => {
{
unsafe {
core::arch::asm!(
concat!("msr ", core::stringify!($dest), ", {imm}"),
imm = const $imm,
options(nomem, nostack),
)
}
}
};
(reg $dest:ident, imm $imm:expr, $($barrier:literal),+) => {
{
unsafe {
core::arch::asm!(
concat!("msr ", core::stringify!($dest), ", {imm}"),
$($barrier,)+
imm = const $imm,
options(nomem, nostack),
)
}
}
};
(reg $dest:ident, reg $src:ident) => {
{
unsafe {
core::arch::asm!(
concat!("msr ", core::stringify!($dest), ", ", core::stringify!($src)),
options(nomem, nostack),
)
}
}
};
(reg $dest:ident, reg $src:ident, $($barrier:literal),+) => {
{
unsafe {
core::arch::asm!(
concat!("msr ", core::stringify!($dest), ", ", core::stringify!($src)),
$($barrier,)+
options(nomem, nostack),
)
}
}
};
(reg $name:ident, $value:expr) => {
{
let v: u64 = $value;
unsafe {
core::arch::asm!(
concat!("msr ", core::stringify!($name), ", {value:x}"),
value = in(reg) v,
options(nomem, nostack),
)
}
}
};
(reg $name:ident, $value:expr, $($barrier:literal),+) => {
{
let v: u64 = $value;
unsafe {
core::arch::asm!(
concat!("msr ", core::stringify!($name), ", {value:x}"),
$($barrier,)+
value = in(reg) v,
options(nomem, nostack),
)
}
}
};
}
impl super::Interrupts for AArch64 {
fn enable_interrupts() {
write_sysreg!(reg daifclr, imm DAIF_WR_IRQ_BIT, "isb sy");
}
fn disable_interrupts() {
write_sysreg!(reg daifset, imm DAIF_WR_IRQ_BIT, "isb sy");
}
fn interrupts_enabled() -> bool {
let daif = read_sysreg!(daif);
daif & 0x80 == 0
}
fn enable_interrupts_and_sleep() {
unsafe {
core::arch::asm!("msr daifclr, {}; wfi", const DAIF_WR_IRQ_BIT, options(nostack, nomem, preserves_flags));
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum AArch64El {
EL1,
EL2,
}
pub fn get_current_el() -> AArch64El {
match read_sysreg!(CurrentEL) & 0xC {
0xC => panic!("EL3 is not supported"),
0x8 => AArch64El::EL2,
0x4 => AArch64El::EL1,
0x0 => panic!("EL0 is not supported"),
_ => unreachable!(),
}
}
impl super::CacheMgmt for AArch64 {
fn flush_data_cache(start: efi::PhysicalAddress, length: u64, flush_type: CpuFlushType) -> Result<(), EfiError> {
flush_data_cache_range(start, length, flush_type);
Ok(())
}
fn cache_writeback_granule() -> u32 {
let ctr_el0 = read_sysreg!(ctr_el0);
let cwg = ((ctr_el0 >> 24) & 0xF) as u32;
if cwg > 0 {
4 << cwg
} else {
crate::base::SIZE_2KB as u32 }
}
}
impl super::Timer for AArch64 {
fn get_timer_value() -> u64 {
read_sysreg!(CNTPCT_EL0)
}
fn get_timer_frequency() -> Option<NonZeroU64> {
match read_sysreg!(CNTFRQ_EL0) {
0 => None,
freq => NonZeroU64::new(freq),
}
}
}
fn clean_data_entry_by_mva(mva: efi::PhysicalAddress) {
unsafe {
core::arch::asm!("dc cvac, {}", in(reg) mva, options(nostack, preserves_flags));
}
}
fn invalidate_data_cache_entry_by_mva(mva: efi::PhysicalAddress) {
unsafe {
core::arch::asm!("dc ivac, {}", in(reg) mva, options(nostack, preserves_flags));
}
}
fn clean_and_invalidate_data_entry_by_mva(mva: efi::PhysicalAddress) {
unsafe {
core::arch::asm!("dc civac, {}", in(reg) mva, options(nostack, preserves_flags));
}
}
fn data_cache_line_len() -> u64 {
let ctr_el0 = read_sysreg!(ctr_el0);
4 << ((ctr_el0 >> 16) & 0xf)
}
fn flush_data_cache_range(start: efi::PhysicalAddress, length: u64, op: CpuFlushType) {
if length == 0 {
return;
}
let cacheline_size = data_cache_line_len();
let cacheline_mask = cacheline_size - 1;
let mut aligned_addr = start & !cacheline_mask;
let end_addr = match start.checked_add(length) {
Some(end_addr) => end_addr,
None => {
debug_assert!(false, "Cache range overflow");
return;
}
};
while aligned_addr < end_addr {
match op {
CpuFlushType::EfiCpuFlushTypeWriteBack => clean_data_entry_by_mva(aligned_addr),
CpuFlushType::EfiCpuFlushTypeInvalidate => invalidate_data_cache_entry_by_mva(aligned_addr),
CpuFlushType::EfiCpuFlushTypeWriteBackInvalidate => clean_and_invalidate_data_entry_by_mva(aligned_addr),
}
match aligned_addr.checked_add(cacheline_size) {
Some(next) => aligned_addr = next,
None => break,
}
}
unsafe {
core::arch::asm!("dsb sy", options(nostack));
}
}