use core::arch::asm;
use ax_memory_addr::{PAGE_SIZE_4K, PhysAddr, VirtAddr};
use loongArch64::register::{
asid, crmd,
ecfg::{self, LineBasedInterrupt},
eentry, pgdh, pgdl,
};
const INVTLB_ADDR_GTRUE_OR_ASID: usize = 0x06;
const TLB_PAIR_SIZE: usize = PAGE_SIZE_4K * 2;
#[cfg(feature = "tls")]
use crate::KernelTlsBase;
#[cfg(feature = "uspace")]
use crate::{InstalledAddressSpace, InstalledAddressSpaceMode};
pub fn address_space_tag_capacity(_cpu_count: usize) -> u32 {
let width = asid::read().asid_width().min(u16::BITS as usize);
1u32.checked_shl(width as u32).unwrap_or(1).max(1)
}
#[cfg(feature = "uspace")]
fn flush_tlb_asid(tag: u16) {
unsafe {
asm!(
"dbar 0; invtlb 0x04, {asid}, $r0",
asid = in(reg) usize::from(tag),
)
}
}
#[cfg(feature = "uspace")]
pub unsafe fn install_user_address_space(address_space: InstalledAddressSpace) {
address_space.validate_architecture_support();
let tagged = matches!(address_space.mode(), InstalledAddressSpaceMode::Tagged)
&& u32::from(address_space.hardware_tag()) < address_space_tag_capacity(1);
if tagged {
flush_tlb_asid(address_space.hardware_tag());
pgdl::set_base(address_space.root().as_usize() as _);
asid::set_asid(usize::from(address_space.hardware_tag()));
} else {
pgdl::set_base(address_space.root().as_usize() as _);
asid::set_asid(0);
flush_tlb(None);
}
}
core::arch::global_asm!(
".balign 16",
".global __axcpu_wait_for_irqs_disabled",
".global __axcpu_loongarch_idle_start",
".global __axcpu_loongarch_idle_exit",
"__axcpu_wait_for_irqs_disabled:",
"__axcpu_loongarch_idle_start:",
"ori $t0, $zero, 4",
"csrxchg $t0, $t0, 0x0",
"idle 0",
"__axcpu_loongarch_idle_exit:",
"ret",
);
unsafe extern "C" {
fn __axcpu_wait_for_irqs_disabled();
}
#[inline]
pub fn enable_irqs() {
crmd::set_ie(true)
}
#[inline]
pub fn disable_irqs() {
crmd::set_ie(false)
}
#[inline]
pub fn irqs_enabled() -> bool {
crmd::read().ie()
}
#[inline]
pub fn set_timer_irq_enabled(enabled: bool) {
set_local_irq_line_enabled(LineBasedInterrupt::TIMER, enabled)
}
#[inline]
fn set_local_irq_line_enabled(line: LineBasedInterrupt, enabled: bool) {
let current = ecfg::read().lie();
let new_value = if enabled {
current | line
} else {
current & !line
};
ecfg::set_lie(new_value);
}
#[inline]
pub fn wait_for_irqs() {
unsafe { asm!("idle 0", options(nomem, nostack)) }
}
#[inline]
pub fn wait_for_irqs_disabled() {
debug_assert!(!irqs_enabled());
unsafe { __axcpu_wait_for_irqs_disabled() }
}
#[inline]
pub fn halt() {
disable_irqs();
unsafe { loongArch64::asm::idle() }
}
#[inline]
pub fn read_user_page_table() -> PhysAddr {
PhysAddr::from(pgdl::read().base())
}
#[inline]
pub fn read_kernel_page_table() -> PhysAddr {
PhysAddr::from(pgdh::read().base())
}
pub unsafe fn write_user_page_table(root_paddr: PhysAddr) {
pgdl::set_base(root_paddr.as_usize() as _);
}
pub unsafe fn write_kernel_page_table(root_paddr: PhysAddr) {
pgdh::set_base(root_paddr.as_usize());
}
#[inline]
pub fn flush_icache_all() {
unsafe { asm!("ibar 0") };
}
#[inline]
pub fn flush_tlb(vaddr: Option<VirtAddr>) {
unsafe {
if let Some(vaddr) = vaddr {
let pair_base = vaddr.as_usize() & !(TLB_PAIR_SIZE - 1);
let current_asid = asid::read().asid();
asm!(
"dbar 0; invtlb {op}, {asid}, {addr}",
op = const INVTLB_ADDR_GTRUE_OR_ASID,
asid = in(reg) current_asid,
addr = in(reg) pair_base,
);
} else {
asm!("dbar 0; invtlb 0x00, $r0, $r0");
}
}
}
#[inline]
pub fn update_mmu_cache(vaddr: VirtAddr) {
flush_tlb(Some(vaddr));
}
#[inline]
pub unsafe fn write_exception_entry_base(eentry: usize) {
ecfg::set_vs(0);
eentry::set_eentry(eentry);
}
#[inline]
pub unsafe fn write_pwc(pwcl: u32, pwch: u32) {
unsafe {
asm!(
include_asm_macros!(),
"csrwr {}, LA_CSR_PWCL",
"csrwr {}, LA_CSR_PWCH",
in(reg) pwcl,
in(reg) pwch
)
}
}
#[inline]
#[cfg(feature = "tls")]
pub fn read_thread_pointer() -> KernelTlsBase {
let address;
unsafe { asm!("move {}, $tp", out(reg) address) };
KernelTlsBase::new(address)
}
#[inline]
#[cfg(feature = "tls")]
pub unsafe fn write_thread_pointer(kernel_tls: KernelTlsBase) {
unsafe { asm!("move $tp, {}", in(reg) kernel_tls.as_usize()) }
}
#[inline]
pub fn enable_fp() {
loongArch64::register::euen::set_fpe(true);
}
pub fn enable_lsx() {
loongArch64::register::euen::set_sxe(true);
}
pub fn enable_lasx() {
loongArch64::register::euen::set_asxe(true);
}
#[cfg(feature = "uspace")]
core::arch::global_asm!(
include_asm_macros!(),
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(feature = "uspace")]
#[inline]
pub unsafe fn user_access_ok_page(_vaddr: usize, _access: crate::UserAccessType) -> bool {
false
}