Add the boot section of LoongArch in OSTD

This commit is contained in:
王英泰 2025-07-08 15:44:54 +08:00 committed by Tate, Hongliang Tian
parent b0b242edbc
commit ce22374b50
5 changed files with 442 additions and 0 deletions

View File

@ -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

View File

@ -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: <https://github.com/torvalds/linux/blob/master/include/linux/efi.h#L417>
const LINUX_EFI_INITRD_MEDIA_GUID: EfiGuid = efi_guid!(
0x5568e427,
0x68fc,
0x4f3d,
[0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68]
);
/// Reference: <https://uefi.org/specs/UEFI/2.10/04_EFI_System_Table.html#devicetree-tables>
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: <https://uefi.org/specs/UEFI/2.10/04_EFI_System_Table.html#id4>
#[repr(C)]
struct EfiTableHeader {
signature: u64,
revision: u32,
headersize: u32,
crc32: u32,
reserved: u32,
}
/// Reference: <https://uefi.org/specs/UEFI/2.10/04_EFI_System_Table.html#efi-configuration-table>
#[repr(C)]
struct EfiConfigurationTable {
guid: EfiGuid,
table: *const core::ffi::c_void,
}
/// Reference: <https://uefi.org/specs/UEFI/2.10/04_EFI_System_Table.html#id6>
#[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<Paddr> {
let table = self.table(&DEVICE_TREE_GUID)?;
Some(table.table as _)
}
}
/// Reference: <https://github.com/torvalds/linux/blob/master/include/linux/efi.h#L1327>
#[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 _))
}
}
}

View File

@ -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<Fdt> = 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<BootloaderFramebufferArg> {
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: <https://docs.kernel.org/arch/loongarch/booting.html#information-passed-from-bootloader-to-kernel>
#[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();
}

View File

@ -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<u32> {
Some(1)
}
pub(crate) fn bringup_all_aps(_info_ptr: *const PerApRawInfo, _pr_ptr: Paddr, _num_cpus: u32) {
unimplemented!()
}

View File

@ -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;