diff --git a/ostd/src/arch/loongarch/boot/boot.S b/ostd/src/arch/loongarch/boot/boot.S new file mode 100644 index 000000000..b0c37cc11 --- /dev/null +++ b/ostd/src/arch/loongarch/boot/boot.S @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: MPL-2.0 */ + +.equ LOONGARCH_CSR_CRMD, 0x0 /* Current mode */ +.equ LOONGARCH_CSR_PRMD, 0x1 /* Previous mode */ +.equ LOONGARCH_CSR_EUEN, 0x2 /* Extended unit enable */ +.equ LOONGARCH_CSR_PGDL, 0x19 /* Page table base address when VA[47] = 0 */ +.equ LOONGARCH_CSR_PGDH, 0x1a /* Page table base address when VA[47] = 1 */ +.equ LOONGARCH_CSR_PGD, 0x1b /* Page table base */ +.equ LOONGARCH_CSR_PWCL, 0x1c /* Page table walk control low */ +.equ LOONGARCH_CSR_PWCH, 0x1d /* Page table walk control high */ +.equ LOONGARCH_CSR_STLBPS, 0x1e /* STLB page size */ +.equ LOONGARCH_CSR_CPUID, 0x20 /* CPUID */ +.equ LOONGARCH_CSR_TLBRENTRY, 0x88 /* TLB refill exception entry */ +.equ LOONGARCH_CSR_TLBRBADV, 0x89 /* TLB refill badvaddr */ +.equ LOONGARCH_CSR_TLBRERA, 0x8a /* TLB refill ERA */ +.equ LOONGARCH_CSR_TLBRSAVE, 0x8b /* KScratch for TLB refill exception */ +.equ LOONGARCH_CSR_TLBRELO0, 0x8c /* TLB refill entrylo0 */ +.equ LOONGARCH_CSR_TLBRELO1, 0x8d /* TLB refill entrylo1 */ +.equ LOONGARCH_CSR_TLBREHI, 0x8e /* TLB refill entryhi */ +.equ LOONGARCH_CSR_DMW0, 0x180 /* Direct mapping window 0 */ +.equ LOONGARCH_CSR_DMW1, 0x181 /* Direct mapping window 1 */ +.equ LOONGARCH_CSR_DMW2, 0x182 /* Direct mapping window 2 */ +.equ LOONGARCH_CSR_DMW3, 0x183 /* Direct mapping window 3 */ +.equ SAVE_TLBREFILL_T1, 0x30 /* Save 0 */ +.equ LOONGARCH_CSR_SAVE1, 0x31 /* Save 1 */ +.equ LOONGARCH_CSR_SAVE2, 0x32 /* Save 2 */ +.equ LOONGARCH_CSR_SAVE3, 0x33 /* Save 3 */ +.equ LOONGARCH_CSR_SAVE4, 0x34 /* Save 4 */ +.equ LOONGARCH_CSR_SAVE5, 0x35 /* Save 5 */ +.equ LOONGARCH_CSR_SAVE6, 0x36 /* Save 6 */ +.equ LOONGARCH_CSR_SAVE7, 0x37 /* Save 7 */ + +.section ".boot", "awx", @progbits +.globl _start +_start: + # Set DMW0 (kernel) + li.d $t0, 0x9000000000000011 # CA, PLV0, 0x9000_xxxx_xxxx_xxxx + csrwr $t0, LOONGARCH_CSR_DMW0 + + # Set DMW1 (kernel), temporary use + li.d $t0, 0x0000000000000011 # CA, PLV0, 0x0000_xxxx_xxxx_xxxx + csrwr $t0, LOONGARCH_CSR_DMW1 + + # Set DMW2 (device) + li.d $t0, 0x8000000000000001 # UA, PLV0, 0x8000_xxxx_xxxx_xxxx + csrwr $t0, LOONGARCH_CSR_DMW2 + + # Disable floating point unit etc. + li.w $t0, 0x00000000 # FPE=0, SXE=0, ASXE=0, BTE=0 + csrwr $t0, LOONGARCH_CSR_EUEN + + # Set the first level (root level) page table size + li.w $t0, 0x0000000c # 4KB + csrwr $t0, LOONGARCH_CSR_STLBPS + + # Set the page size of the TLB refill + # Now we only support the 4KB page size + li.w $t0, 0x0000000c # 4KB + csrwr $t0, LOONGARCH_CSR_TLBREHI + + # Set the structure of page table + # PTbase=12, PTwidth=9, Dir1_base=12 + 9, Dir1_width=9, Dir2_base=12 + 9 + 9, Dir2_width=9, PTEWidth=0 (64 bits) + li.w $t0, 12 | 9 << 5 | 21 << 10 | 9 << 15 | 30 << 20 | 9 << 25 + csrwr $t0, LOONGARCH_CSR_PWCL + # Dir3_base=12 + 9 + 9 + 9, Dir3_width=9 + li.w $t0, 39 | 9 << 6 + csrwr $t0, LOONGARCH_CSR_PWCH + + # Set the boot page table + la $t0, boot_l4pt + csrwr $t0, LOONGARCH_CSR_PGDL + la $t0, boot_l4pt + csrwr $t0, LOONGARCH_CSR_PGDH + + # Initialize TLB + invtlb 0, $zero, $zero + + # Enable address translation and disable interrupts + li.w $t0, 0x00000010 # PLV=0, IE=0, PG=1 + csrwr $t0, LOONGARCH_CSR_CRMD + li.w $t0, 0x00000000 # PPLV=0, PIE=0, PWE=0 + csrwr $t0, LOONGARCH_CSR_PRMD + + # Set the entry of TLB refill exception + la.global $t0, _handle_tlb_refill + # Convert the address to physical address +.extern KERNEL_VMA_OFFSET + la.global $t1, KERNEL_VMA_OFFSET + sub.d $t0, $t0, $t1 + csrwr $t0, LOONGARCH_CSR_TLBRENTRY + + # Update SP/PC to use the virtual address + la $sp, boot_stack_top + add.d $sp, $sp, $t1 + la.global $t0, _start_virt + jr $t0 + +.balign 4096 +.globl boot_l4pt +boot_l4pt: + .zero 8 * 512 + +.section ".boot.stack", "aw", @nobits +.globl boot_stack_bottom +boot_stack_bottom: + .balign 4096 + .skip 0x40000 # 256 KiB +.globl boot_stack_top +boot_stack_top: + +# From here, we're in the .text section: we no longer use physical address. +.text +.globl _start_virt +_start_virt: + # Unset DMW1 (kernel) + csrwr $zero, LOONGARCH_CSR_DMW1 + + # Initialize r21 to the CPU-local start address. +.extern __cpu_local_start + la.global $r21, __cpu_local_start + + # Jump to rust loongarch_boot + la.global $t0, loongarch_boot + jr $t0 + +.balign 4096 +.text +.globl _handle_tlb_refill +_handle_tlb_refill: + # Save $t0, $t1 + csrwr $t0, LOONGARCH_CSR_TLBRSAVE + csrwr $t1, SAVE_TLBREFILL_T1 + + # Read PGD + csrrd $t0, LOONGARCH_CSR_PGD + + # Walk page table + lddir $t0, $t0, 3 + andi $t1, $t0, 0x1 + beqz $t1, _invalid_pte + li.d $t1, 0x1 + andn $t0, $t0, $t1 + + lddir $t0, $t0, 2 + andi $t1, $t0, 0x1 + beqz $t1, _invalid_pte + li.d $t1, 0x1 + andn $t0, $t0, $t1 + + lddir $t0, $t0, 1 + andi $t1, $t0, 0x1 + beqz $t1, _invalid_pte + li.d $t1, 0x1 + andn $t0, $t0, $t1 + + # Load PTEs + ldpte $t0, 0 + ldpte $t0, 1 + b _fill_tlb + +_invalid_pte: + # For invalid PTE, set the PTE to 0, which is valid. + csrwr $zero, LOONGARCH_CSR_TLBRELO0 + csrwr $zero, LOONGARCH_CSR_TLBRELO1 + +_fill_tlb: + # Fill TLB + tlbfill + + # Restore $t0, $t1 + csrrd $t0, LOONGARCH_CSR_TLBRSAVE + csrrd $t1, SAVE_TLBREFILL_T1 + + ertn diff --git a/ostd/src/arch/loongarch/boot/efi.rs b/ostd/src/arch/loongarch/boot/efi.rs new file mode 100644 index 000000000..d3f5189f3 --- /dev/null +++ b/ostd/src/arch/loongarch/boot/efi.rs @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MPL-2.0 + +use crate::mm::{paddr_to_vaddr, Paddr}; + +macro_rules! efi_guid { + ($a:expr, $b:expr, $c:expr, $d:expr) => {{ + let a = ($a as u32).to_le_bytes(); // u32 -> [u8; 4] + let b = ($b as u16).to_le_bytes(); // u16 -> [u8; 2] + let c = ($c as u16).to_le_bytes(); // u16 -> [u8; 2] + let d = $d; + EfiGuid { + b: [ + a[0], a[1], a[2], a[3], b[0], b[1], c[0], c[1], d[0], d[1], d[2], d[3], d[4], d[5], + d[6], d[7], + ], + } + }}; +} + +/// Reference: +const LINUX_EFI_INITRD_MEDIA_GUID: EfiGuid = efi_guid!( + 0x5568e427, + 0x68fc, + 0x4f3d, + [0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68] +); + +/// Reference: +const DEVICE_TREE_GUID: EfiGuid = efi_guid!( + 0xb1b621d5, + 0xf19c, + 0x41a5, + [0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0] +); + +#[repr(C)] +#[derive(Debug, PartialEq, Eq)] +struct EfiGuid { + b: [u8; 16], +} + +/// Reference: +#[repr(C)] +struct EfiTableHeader { + signature: u64, + revision: u32, + headersize: u32, + crc32: u32, + reserved: u32, +} + +/// Reference: +#[repr(C)] +struct EfiConfigurationTable { + guid: EfiGuid, + table: *const core::ffi::c_void, +} + +/// Reference: +#[repr(C)] +pub(super) struct EfiSystemTable { + hdr: EfiTableHeader, + fw_vendor: u64, // physical addr of CHAR16* + fw_revision: u32, + con_in_handle: u64, + con_in: *const u64, + con_out_handle: u64, + con_out: *const u64, + stderr_handle: u64, + stderr_placeholder: u64, + runtime: *const u64, + boottime: *const u64, + nr_tables: u64, + tables: *const EfiConfigurationTable, +} + +// SAFETY: The `EfiSystemTable` structure is only accessed in a read-only manner +// during early EFI initialization. The raw pointers it contains are not written +// to across threads, so it is safe to mark this type as thread-safe. +unsafe impl Sync for EfiSystemTable {} + +impl EfiSystemTable { + fn table(&self, guid: &EfiGuid) -> Option<&EfiConfigurationTable> { + for i in 0..self.nr_tables as usize { + let table = unsafe { + &*(paddr_to_vaddr(self.tables.add(i) as _) as *const EfiConfigurationTable) + }; + if table.guid == *guid { + return Some(table); + } + } + None + } + + pub(super) fn initrd(&self) -> Option<&EfiInitrd> { + let table = self.table(&LINUX_EFI_INITRD_MEDIA_GUID)?; + Some(unsafe { &*(paddr_to_vaddr(table.table as _) as *const EfiInitrd) }) + } + + pub(super) fn device_tree(&self) -> Option { + let table = self.table(&DEVICE_TREE_GUID)?; + Some(table.table as _) + } +} + +/// Reference: +#[repr(C)] +pub(super) struct EfiInitrd { + base: u64, + size: u64, +} + +impl EfiInitrd { + pub(super) fn range(&self) -> Option<(usize, usize)> { + if self.size == 0 { + None + } else { + Some((self.base as _, (self.base + self.size) as _)) + } + } +} diff --git a/ostd/src/arch/loongarch/boot/mod.rs b/ostd/src/arch/loongarch/boot/mod.rs new file mode 100644 index 000000000..12b1bb102 --- /dev/null +++ b/ostd/src/arch/loongarch/boot/mod.rs @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! The LoongArch boot module defines the entrypoints of Asterinas. + +mod efi; +pub mod smp; +use core::{arch::global_asm, ffi::CStr}; + +use fdt::Fdt; +use spin::Once; + +use crate::{ + arch::boot::efi::EfiSystemTable, + boot::{ + memory_region::{MemoryRegion, MemoryRegionArray, MemoryRegionType}, + BootloaderAcpiArg, BootloaderFramebufferArg, + }, + mm::paddr_to_vaddr, +}; + +global_asm!(include_str!("boot.S")); + +static EFI_SYSTEM_TABLE: Once<&'static EfiSystemTable> = Once::new(); + +/// The Flattened Device Tree of the platform. +pub static DEVICE_TREE: Once = Once::new(); + +fn parse_bootloader_name() -> &'static str { + "Unknown" +} + +fn parse_initramfs() -> Option<&'static [u8]> { + let Some((start, end)) = parse_initramfs_range() else { + return None; + }; + + let base_va = paddr_to_vaddr(start); + let length = end - start; + Some(unsafe { core::slice::from_raw_parts(base_va as *const u8, length) }) +} + +fn parse_acpi_arg() -> BootloaderAcpiArg { + BootloaderAcpiArg::NotProvided +} + +fn parse_framebuffer_info() -> Option { + None +} + +fn parse_memory_regions() -> MemoryRegionArray { + let mut regions = MemoryRegionArray::new(); + + for region in DEVICE_TREE.get().unwrap().memory().regions() { + if region.size.unwrap_or(0) > 0 { + regions + .push(MemoryRegion::new( + region.starting_address as usize, + region.size.unwrap(), + MemoryRegionType::Usable, + )) + .unwrap(); + } + } + + // Add the kernel region. + regions.push(MemoryRegion::kernel()).unwrap(); + + // Add the initramfs region. + if let Some((start, end)) = parse_initramfs_range() { + regions + .push(MemoryRegion::new( + start, + end - start, + MemoryRegionType::Module, + )) + .unwrap(); + } + + regions.into_non_overlapping() +} + +fn parse_initramfs_range() -> Option<(usize, usize)> { + EFI_SYSTEM_TABLE.get().unwrap().initrd()?.range() +} + +/// Checks the LoongArch CPU configuration using `cpucfg` instruction. +fn check_cpu_config() { + let palen = loongArch64::cpu::get_palen(); + let valen = loongArch64::cpu::get_valen(); + let support_iocsr = loongArch64::cpu::get_support_iocsr(); + + // Now we only support the 48 bits PA width. + assert_eq!(palen, 48); + // Now we only support the 48 bits VA width. + assert_eq!(valen, 48); + // Now we require IOCSR support be present. + assert!(support_iocsr); +} + +/// The entry point of the Rust code portion of Asterinas. +/// +/// Reference: +#[no_mangle] +pub extern "C" fn loongarch_boot(_efi_boot: usize, cmdline_paddr: usize, systab_paddr: usize) -> ! { + check_cpu_config(); + + let systab_ptr = paddr_to_vaddr(systab_paddr) as *const EfiSystemTable; + let systab = unsafe { &*(systab_ptr) }; + EFI_SYSTEM_TABLE.call_once(|| systab); + + let device_tree_ptr = + paddr_to_vaddr(systab.device_tree().expect("device tree not found")) as *const u8; + let fdt = unsafe { fdt::Fdt::from_ptr(device_tree_ptr).unwrap() }; + DEVICE_TREE.call_once(|| fdt); + + let cmdline_ptr = paddr_to_vaddr(cmdline_paddr) as *const i8; + let cmdline = unsafe { CStr::from_ptr(cmdline_ptr) }.to_str(); + + use crate::boot::{call_ostd_main, EarlyBootInfo, EARLY_INFO}; + + EARLY_INFO.call_once(|| EarlyBootInfo { + bootloader_name: parse_bootloader_name(), + kernel_cmdline: cmdline.unwrap_or(""), + initramfs: parse_initramfs(), + acpi_arg: parse_acpi_arg(), + framebuffer_arg: parse_framebuffer_info(), + memory_regions: parse_memory_regions(), + }); + + call_ostd_main(); +} diff --git a/ostd/src/arch/loongarch/boot/smp.rs b/ostd/src/arch/loongarch/boot/smp.rs new file mode 100644 index 000000000..84ca6dbbc --- /dev/null +++ b/ostd/src/arch/loongarch/boot/smp.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Multiprocessor Boot Support + +use crate::{boot::smp::PerApRawInfo, mm::Paddr}; + +pub(crate) fn count_processors() -> Option { + Some(1) +} + +pub(crate) fn bringup_all_aps(_info_ptr: *const PerApRawInfo, _pr_ptr: Paddr, _num_cpus: u32) { + unimplemented!() +} diff --git a/ostd/src/lib.rs b/ostd/src/lib.rs index 748067be5..a20a2b769 100644 --- a/ostd/src/lib.rs +++ b/ostd/src/lib.rs @@ -32,6 +32,9 @@ pub mod arch; #[cfg(target_arch = "riscv64")] #[path = "arch/riscv/mod.rs"] pub mod arch; +#[cfg(target_arch = "loongarch64")] +#[path = "arch/loongarch/mod.rs"] +pub mod arch; pub mod boot; pub mod bus; pub mod console;