rv64: boot into upper half

This commit is contained in:
Mark Poliakov 2025-01-17 02:25:49 +02:00
parent 07458f33e4
commit 86eb2d3252
36 changed files with 1262 additions and 20 deletions

16
Cargo.lock generated
View File

@ -967,6 +967,7 @@ dependencies = [
"kernel-arch-hosted",
"kernel-arch-i686",
"kernel-arch-interface",
"kernel-arch-riscv64",
"kernel-arch-x86_64",
]
@ -1018,6 +1019,20 @@ dependencies = [
"yggdrasil-abi",
]
[[package]]
name = "kernel-arch-riscv64"
version = "0.1.0"
dependencies = [
"bitflags 2.6.0",
"device-api",
"kernel-arch-interface",
"libk-mm-interface",
"memtables",
"static_assertions",
"tock-registers 0.9.0",
"yggdrasil-abi",
]
[[package]]
name = "kernel-arch-x86"
version = "0.1.0"
@ -2658,6 +2673,7 @@ dependencies = [
"kernel-arch-aarch64",
"kernel-arch-i686",
"kernel-arch-interface",
"kernel-arch-riscv64",
"kernel-arch-x86",
"kernel-arch-x86_64",
"libk",

View File

@ -16,8 +16,9 @@ members = [
"lib/abi",
"lib/libyalloc",
"lib/runtime",
"lib/qemu"
, "lib/abi-serde"]
"lib/qemu",
"lib/abi-serde"
]
[workspace.dependencies]
chrono = { version = "0.4.38", default-features = false, features = ["alloc"] }
@ -53,6 +54,7 @@ abi-generator.path = "tool/abi-generator"
# Kernel parts
kernel-arch-interface.path = "kernel/arch/interface"
kernel-arch-aarch64.path = "kernel/arch/aarch64"
kernel-arch-riscv64.path = "kernel/arch/riscv64"
kernel-arch-x86_64.path = "kernel/arch/x86_64"
kernel-arch-i686.path = "kernel/arch/i686"
kernel-arch-x86.path = "kernel/arch/x86"

View File

@ -0,0 +1,57 @@
ENTRY(__rv64_entry);
KERNEL_PHYS_BASE = 0x80000000;
KERNEL_VIRT_OFFSET = 0xFFFFFFF000000000;
SECTIONS {
. = KERNEL_PHYS_BASE;
PROVIDE(__kernel_start = . + KERNEL_VIRT_OFFSET);
.text.entry : {
*(.text.entry)
}
. = ALIGN(16);
. = . + KERNEL_VIRT_OFFSET;
.text : AT(. - KERNEL_VIRT_OFFSET) {
KEEP(*(.text.vectors));
*(.text*)
}
. = ALIGN(4K);
.rodata : AT(. - KERNEL_VIRT_OFFSET) {
*(.rodata*)
*(.eh_frame*)
. = ALIGN(16);
PROVIDE(__init_array_start = .);
KEEP(*(.init_array*))
PROVIDE(__init_array_end = .);
}
. = ALIGN(4K);
.data.tables : AT (. - KERNEL_VIRT_OFFSET) {
KEEP(*(.data.tables))
}
. = ALIGN(4K);
.data : AT(. - KERNEL_VIRT_OFFSET) {
*(.data*)
. = ALIGN(8);
PROVIDE(__global_pointer = . + 0x800 - KERNEL_VIRT_OFFSET);
*(.got*)
}
. = ALIGN(4K);
PROVIDE(__bss_start_phys = . - KERNEL_VIRT_OFFSET);
.bss : AT(. - KERNEL_VIRT_OFFSET) {
*(COMMON)
*(.bss*)
}
. = ALIGN(4K);
PROVIDE(__bss_end_phys = . - KERNEL_VIRT_OFFSET);
PROVIDE(__bss_size = __bss_end_phys - __bss_start_phys);
PROVIDE(__kernel_end = .);
};

View File

@ -0,0 +1,25 @@
{
"arch": "riscv64",
"os": "none",
"abi": "softfloat",
"cpu": "generic-rv64",
"llvm-target": "riscv64",
"data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
"max-atomic-width": 64,
"target-pointer-width": "64",
"features": "+m,+a,+c",
"disable-redzone": true,
"executables": true,
"panic-strategy": "abort",
"dynamic-linking": true,
"relocation-model": "pic",
"eh-frame-header": false,
"crt-objects-fallback": "false",
"emit-debug-gdb-scripts": false,
"llvm-abiname": "lp64",
"linker": "rust-lld",
"linker-flavor": "ld.lld"
}

View File

@ -53,6 +53,9 @@ aarch64-cpu.workspace = true
device-tree.workspace = true
kernel-arch-aarch64.workspace = true
[target.'cfg(target_arch = "riscv64")'.dependencies]
kernel-arch-riscv64.workspace = true
[target.'cfg(target_arch = "x86_64")'.dependencies]
yboot-proto.workspace = true
kernel-arch-x86_64.workspace = true
@ -81,6 +84,7 @@ kernel-arch-x86_64.workspace = true
kernel-arch-i686.workspace = true
kernel-arch-x86.workspace = true
kernel-arch-aarch64.workspace = true
kernel-arch-riscv64.workspace = true
[features]
default = ["fb_console"]

View File

@ -3,21 +3,22 @@ name = "kernel-arch"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[target.'cfg(all(target_os = "none", target_arch = "x86_64"))'.dependencies]
kernel-arch-x86_64 = { path = "x86_64" }
kernel-arch-x86_64.path = "x86_64"
[target.'cfg(all(target_os = "none", target_arch = "aarch64"))'.dependencies]
kernel-arch-aarch64 = { path = "aarch64" }
kernel-arch-aarch64.path = "aarch64"
[target.'cfg(all(target_os = "none", target_arch = "x86"))'.dependencies]
kernel-arch-i686 = { path = "i686" }
kernel-arch-i686.path = "i686"
[target.'cfg(all(target_os = "none", target_arch = "riscv64"))'.dependencies]
kernel-arch-riscv64.path = "riscv64"
[target.'cfg(not(target_os = "none"))'.dependencies]
kernel-arch-hosted = { path = "hosted" }
kernel-arch-hosted.path = "hosted"
[dependencies]
kernel-arch-interface = { path = "interface" }
kernel-arch-interface.path = "interface"
cfg-if.workspace = true

View File

@ -0,0 +1,18 @@
[package]
name = "kernel-arch-riscv64"
version = "0.1.0"
edition = "2024"
[dependencies]
yggdrasil-abi.workspace = true
kernel-arch-interface.workspace = true
libk-mm-interface.workspace = true
memtables.workspace = true
device-api = { workspace = true, features = ["derive"] }
tock-registers.workspace = true
bitflags.workspace = true
static_assertions.workspace = true
[lints]
workspace = true

View File

@ -0,0 +1,54 @@
use core::marker::PhantomData;
use kernel_arch_interface::{
mem::{KernelTableManager, PhysicalMemoryAllocator},
task::{TaskContext, UserContextInfo},
};
use libk_mm_interface::address::PhysicalAddress;
use yggdrasil_abi::error::Error;
pub struct TaskContextImpl<K, PA> {
_pd: PhantomData<(K, PA)>,
}
impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddress>>
TaskContext<K, PA> for TaskContextImpl<K, PA>
{
const USER_STACK_EXTRA_ALIGN: usize = 0;
const SIGNAL_STACK_EXTRA_ALIGN: usize = 0;
fn user(context: UserContextInfo) -> Result<Self, Error> {
let _ = context;
todo!()
}
fn kernel(entry: extern "C" fn(usize) -> !, arg: usize) -> Result<Self, Error> {
let _ = entry;
let _ = arg;
todo!()
}
fn set_thread_pointer(&self, tp: usize) {
let _ = tp;
todo!()
}
fn align_stack_for_entry(sp: usize) -> usize {
let _ = sp;
todo!()
}
unsafe fn enter(&self) -> ! {
todo!()
}
unsafe fn switch(&self, from: &Self) {
let _ = from;
todo!()
}
unsafe fn switch_and_drop(&self, thread: *const ()) {
let _ = thread;
todo!()
}
}

View File

@ -0,0 +1,109 @@
#![feature(decl_macro)]
#![no_std]
extern crate alloc;
use alloc::vec::Vec;
use device_api::interrupt::{LocalInterruptController, MessageInterruptController};
use kernel_arch_interface::{
cpu::{CpuImpl, IpiQueue},
task::Scheduler,
Architecture,
};
pub mod mem;
pub use mem::{KernelTableManagerImpl, ProcessAddressSpaceImpl};
pub mod context;
pub use context::TaskContextImpl;
use registers::MSTATUS;
use tock_registers::interfaces::{ReadWriteable, Readable};
pub mod registers;
pub struct ArchitectureImpl;
impl Architecture for ArchitectureImpl {
type PerCpuData = ();
type CpuFeatures = ();
type BreakpointType = u32;
const BREAKPOINT_VALUE: Self::BreakpointType = 0;
fn halt() -> ! {
loop {}
}
unsafe fn set_local_cpu(cpu: *mut ()) {
let _ = cpu;
loop {}
}
fn local_cpu() -> *mut () {
loop {}
}
unsafe fn init_local_cpu<S: Scheduler + 'static>(id: Option<u32>, data: Self::PerCpuData) {
let _ = id;
let _ = data;
loop {}
}
unsafe fn init_ipi_queues(queues: Vec<IpiQueue<Self>>) {
let _ = queues;
loop {}
}
fn ipi_queue(cpu_id: u32) -> Option<&'static IpiQueue<Self>> {
let _ = cpu_id;
loop {}
}
#[inline]
unsafe fn set_interrupt_mask(mask: bool) -> bool {
let old = Self::interrupt_mask();
if mask {
MSTATUS.modify(MSTATUS::MIE::CLEAR);
} else {
MSTATUS.modify(MSTATUS::MIE::SET);
}
old
}
#[inline]
fn interrupt_mask() -> bool {
MSTATUS.matches_all(MSTATUS::MIE::SET)
}
fn wait_for_interrupt() {
loop {}
}
fn cpu_count() -> usize {
loop {}
}
fn cpu_index<S: Scheduler + 'static>() -> u32 {
loop {}
}
fn cpu_enabled_features<S: Scheduler>(cpu: &CpuImpl<Self, S>) -> Option<&Self::CpuFeatures> {
let _ = cpu;
loop {}
}
fn cpu_available_features<S: Scheduler>(cpu: &CpuImpl<Self, S>) -> Option<&Self::CpuFeatures> {
let _ = cpu;
loop {}
}
fn local_interrupt_controller() -> Option<&'static dyn LocalInterruptController> {
loop {}
}
fn message_interrupt_controller() -> Option<&'static dyn MessageInterruptController> {
loop {}
}
fn idle_task() -> extern "C" fn(usize) -> ! {
loop {}
}
}

View File

@ -0,0 +1,120 @@
use core::marker::PhantomData;
use kernel_arch_interface::{
mem::{DeviceMemoryAttributes, KernelTableManager, RawDeviceMemoryMapping},
split_spinlock,
};
use libk_mm_interface::{
address::PhysicalAddress,
process::ProcessAddressSpaceManager,
table::{page_index, MapAttributes, TableAllocator},
};
use static_assertions::const_assert_eq;
use table::{L1, L2};
use yggdrasil_abi::error::Error;
pub use memtables::riscv64::FixedTables;
pub mod table;
split_spinlock! {
use crate::ArchitectureImpl;
use crate::mem::FixedTables;
use libk_mm_interface::KernelImageObject;
#[link_section = ".data.tables"]
#[used]
static KERNEL_TABLES: KernelImageObject<FixedTables> =
unsafe { KernelImageObject::new(FixedTables::zeroed()) };
}
pub const KERNEL_VIRT_OFFSET: usize = 0xFFFFFFF0_00000000;
pub const KERNEL_PHYS_BASE: usize = 0x80000000;
pub const SIGN_EXTEND_MASK: usize = 0xFFFFFFC0_00000000;
pub const KERNEL_START_L1I: usize = page_index::<L1>(KERNEL_VIRT_OFFSET + KERNEL_PHYS_BASE);
pub const KERNEL_L2I: usize = page_index::<L2>(KERNEL_VIRT_OFFSET + KERNEL_PHYS_BASE);
const_assert_eq!(KERNEL_START_L1I, 450);
const_assert_eq!(KERNEL_L2I, 0);
/// Any VAs above this one are sign-extended
pub const USER_BOUNDARY: usize = 0x40_00000000;
#[derive(Debug)]
pub struct KernelTableManagerImpl;
pub struct ProcessAddressSpaceImpl<TA: TableAllocator> {
_pd: PhantomData<TA>,
}
impl KernelTableManager for KernelTableManagerImpl {
fn virtualize(phys: u64) -> usize {
let _ = phys;
loop {}
}
fn physicalize(virt: usize) -> u64 {
let _ = virt;
loop {}
}
unsafe fn map_device_pages(
base: u64,
count: usize,
attrs: DeviceMemoryAttributes,
) -> Result<RawDeviceMemoryMapping<Self>, Error> {
let _ = base;
let _ = count;
let _ = attrs;
loop {}
}
unsafe fn unmap_device_pages(mapping: &RawDeviceMemoryMapping<Self>) {
let _ = mapping;
loop {}
}
unsafe fn unmap_physical_address(virt: usize) {
let _ = virt;
loop {}
}
}
impl<TA: TableAllocator> ProcessAddressSpaceManager<TA> for ProcessAddressSpaceImpl<TA> {
const LOWER_LIMIT_PFN: usize = 0;
const UPPER_LIMIT_PFN: usize = 0;
fn new() -> Result<Self, Error> {
todo!()
}
unsafe fn map_page(
&mut self,
address: usize,
physical: PhysicalAddress,
flags: MapAttributes,
) -> Result<(), Error> {
let _ = address;
let _ = physical;
let _ = flags;
todo!()
}
unsafe fn unmap_page(&mut self, address: usize) -> Result<PhysicalAddress, Error> {
let _ = address;
todo!()
}
fn translate(&self, address: usize) -> Result<(PhysicalAddress, MapAttributes), Error> {
let _ = address;
todo!()
}
fn as_address_with_asid(&self) -> u64 {
todo!()
}
unsafe fn clear(&mut self) {
todo!()
}
}

View File

@ -0,0 +1,169 @@
use core::{
marker::PhantomData,
ops::{Index, IndexMut},
};
use bitflags::bitflags;
use libk_mm_interface::{
address::PhysicalAddress,
pointer::{PhysicalRef, PhysicalRefMut},
table::{EntryLevel, NextPageTable, NonTerminalEntryLevel, TableAllocator},
};
use yggdrasil_abi::error::Error;
use super::KernelTableManagerImpl;
bitflags! {
pub struct PageAttributes: u64 {
const N = 1 << 63;
/// Dirty bit
const D = 1 << 7;
/// Access bit
const A = 1 << 6;
/// Global mapping bit, implies all lower levels are also global
const G = 1 << 5;
/// U-mode access permission
const U = 1 << 4;
/// Execute permission
const X = 1 << 3;
/// Write permission
const W = 1 << 2;
/// Read-permission
const R = 1 << 1;
/// Valid bit
const V = 1 << 0;
}
// X W R Meaning
// 0 0 0 Pointer to next level of page table
// 0 0 1 Read-only page
// 0 1 0 ---
// 0 1 1 Read-write page
// 1 0 0 Execute only
// 1 0 1 Read-execute page
// 1 1 0 ---
// 1 1 1 Read-write-execute page
}
/// L3 - entry is 4KiB
#[derive(Debug, Clone, Copy)]
pub struct L3;
/// L2 - entry is 2MiB
#[derive(Debug, Clone, Copy)]
pub struct L2;
/// L1 - entry is 1GiB
#[derive(Debug, Clone, Copy)]
pub struct L1;
impl EntryLevel for L3 {
const SHIFT: usize = 12;
}
impl EntryLevel for L2 {
const SHIFT: usize = 21;
}
impl EntryLevel for L1 {
const SHIFT: usize = 30;
}
#[repr(C, align(0x1000))]
pub struct PageTable<L: EntryLevel> {
entries: [PageEntry<L>; 512],
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PageEntry<L: EntryLevel>(u64, PhantomData<L>);
impl NonTerminalEntryLevel for L1 {
type NextLevel = L2;
}
impl NonTerminalEntryLevel for L2 {
type NextLevel = L3;
}
impl<L: EntryLevel> PageTable<L> {
pub const fn zeroed() -> Self {
Self {
entries: [PageEntry::INVALID; 512],
}
}
}
impl<L: EntryLevel> PageEntry<L> {
pub const INVALID: Self = Self(0, PhantomData);
pub const fn is_present(&self) -> bool {
self.0 & PageAttributes::V.bits() != 0
}
pub fn attributes(self) -> PageAttributes {
PageAttributes::from_bits_retain(self.0)
}
}
impl<L: NonTerminalEntryLevel + 'static> NextPageTable for PageTable<L> {
type NextLevel = PageTable<L::NextLevel>;
type TableRef = PhysicalRef<'static, PageTable<L::NextLevel>, KernelTableManagerImpl>;
type TableRefMut = PhysicalRefMut<'static, PageTable<L::NextLevel>, KernelTableManagerImpl>;
fn get(&self, _index: usize) -> Option<Self::TableRef> {
loop {}
}
fn get_mut(&mut self, _index: usize) -> Option<Self::TableRefMut> {
loop {}
}
fn get_mut_or_alloc<TA: TableAllocator>(
&mut self,
_index: usize,
) -> Result<Self::TableRefMut, Error> {
loop {}
}
}
impl<L: NonTerminalEntryLevel> PageEntry<L> {
pub fn block(address: PhysicalAddress, attrs: PageAttributes) -> Self {
// TODO validate address alignment
Self(
(address.into_u64() >> 2) | (PageAttributes::R | PageAttributes::V | attrs).bits(),
PhantomData,
)
}
pub fn table(address: PhysicalAddress, mut attrs: PageAttributes) -> Self {
attrs.remove(PageAttributes::R | PageAttributes::W | PageAttributes::X);
Self(
(address.into_u64() >> 2) | (PageAttributes::V | attrs).bits(),
PhantomData,
)
}
}
impl PageEntry<L3> {
pub fn page(address: PhysicalAddress, attrs: PageAttributes) -> Self {
Self(
(address.into_u64() >> 2) | (PageAttributes::R | PageAttributes::V | attrs).bits(),
PhantomData,
)
}
pub fn as_page(&self) -> Option<PhysicalAddress> {
loop {}
}
}
impl<L: EntryLevel> Index<usize> for PageTable<L> {
type Output = PageEntry<L>;
fn index(&self, index: usize) -> &Self::Output {
&self.entries[index]
}
}
impl<L: EntryLevel> IndexMut<usize> for PageTable<L> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.entries[index]
}
}

View File

@ -0,0 +1,183 @@
macro impl_csr_read($struct:ident, $repr:ty, $reg:ident, $register:ty) {
impl tock_registers::interfaces::Readable for $struct {
type T = $repr;
type R = $register;
#[inline]
fn get(&self) -> $repr {
let mut value: $repr;
unsafe {
core::arch::asm!(concat!("csrr {0}, ", stringify!($reg)), out(reg) value);
}
value
}
}
}
macro impl_csr_write($struct:ident, $repr:ty, $reg:ident, $register:ty) {
impl tock_registers::interfaces::Writeable for $struct {
type T = $repr;
type R = $register;
#[inline]
fn set(&self, value: $repr) {
unsafe {
core::arch::asm!(concat!("csrw ", stringify!($reg), ", {0}"), in(reg) value);
}
}
}
}
pub mod misa {
use tock_registers::{interfaces::Readable, register_bitfields};
use super::{impl_csr_read, impl_csr_write};
register_bitfields!(
u64,
pub MISA [
A OFFSET(0) NUMBITS(1) [],
C OFFSET(2) NUMBITS(1) [],
D OFFSET(3) NUMBITS(1) [],
E OFFSET(4) NUMBITS(1) [],
F OFFSET(5) NUMBITS(1) [],
H OFFSET(6) NUMBITS(1) [],
I OFFSET(7) NUMBITS(1) [],
M OFFSET(12) NUMBITS(1) [],
Q OFFSET(16) NUMBITS(1) [],
S OFFSET(17) NUMBITS(1) [],
U OFFSET(18) NUMBITS(1) [],
X OFFSET(23) NUMBITS(1) [],
]
);
pub struct Reg;
impl_csr_read!(Reg, u64, misa, MISA::Register);
impl_csr_write!(Reg, u64, misa, MISA::Register);
impl Reg {
pub fn is_valid(&self) -> bool {
self.get() != 0
}
}
pub const MISA: Reg = Reg;
}
pub mod mstatus {
use tock_registers::register_bitfields;
use super::{impl_csr_read, impl_csr_write};
register_bitfields!(
u64,
pub MSTATUS [
/// Interrupt enable for S-mode
SIE OFFSET(1) NUMBITS(1) [],
/// Interrupt enable for M-mode
MIE OFFSET(3) NUMBITS(1) [],
/// Stored SIE state on S-mode trap delegation
SPIE OFFSET(5) NUMBITS(1) [],
/// U-mode big endian
UBE OFFSET(6) NUMBITS(1) [],
/// TODO: something written here on trap to M-mode
MPIE OFFSET(7) NUMBITS(1) [],
/// TODO: something for nested traps
SPP OFFSET(8) NUMBITS(1) [],
/// Vector register dirty status
VS OFFSET(9) NUMBITS(2) [],
/// Original mode before being trapped into M-mode
MPP OFFSET(11) NUMBITS(2) [
U = 0,
S = 1,
M = 3
],
/// Float register dirty status
FS OFFSET(13) NUMBITS(2) [],
/// U-mode extension dirty status
XS OFFSET(15) NUMBITS(2) [],
/// Effective privilege mode at which loads and stores execute.
///
/// When MPRV = 0, loads and stores behave as normal
/// MPRV = 1, loads/stores are translated and protected
MPRV OFFSET(17) NUMBITS(1) [],
/// Permit supervisor user memory access
///
/// When SUM = 0, S-mode access to pages accessible by U-mode will fault
SUM OFFSET(18) NUMBITS(1) [],
MXR OFFSET(19) NUMBITS(1) [],
/// Trap virtual memory
///
/// When TVM = 1, attempts to read/write satp CSR, execute sfence.vma or sinval.vma
/// in S-mode will raise an illegal instruction exception
TVM OFFSET(20) NUMBITS(1) [],
/// Timeout wait
///
/// When TW = 1, wfi executed in lower privilege level which does not complete
/// within some implementation-specific timeout, raises an illegal
/// instruction exception
TW OFFSET(21) NUMBITS(1) [],
TSR OFFSET(22) NUMBITS(1) [],
/// U-mode XLEN value
UXL OFFSET(32) NUMBITS(2) [],
/// S-mode XLEN value
SXL OFFSET(34) NUMBITS(2) [],
/// S-mode big endian
SBE OFFSET(36) NUMBITS(1) [],
/// M-mode big endian
MBE OFFSET(37) NUMBITS(1) [],
SD OFFSET(63) NUMBITS(1) [],
]
);
pub struct Reg;
impl_csr_read!(Reg, u64, mstatus, MSTATUS::Register);
impl_csr_write!(Reg, u64, mstatus, MSTATUS::Register);
pub const MSTATUS: Reg = Reg;
}
pub mod mepc {
use super::{impl_csr_read, impl_csr_write};
pub struct Reg;
impl_csr_read!(Reg, u64, mepc, ());
impl_csr_write!(Reg, u64, mepc, ());
pub const MEPC: Reg = Reg;
}
pub mod satp {
use tock_registers::register_bitfields;
use super::{impl_csr_read, impl_csr_write};
register_bitfields!(
u64,
pub SATP [
PPN OFFSET(0) NUMBITS(44) [],
ASID OFFSET(44) NUMBITS(16) [],
MODE OFFSET(60) NUMBITS(4) [
Bare = 0,
Sv39 = 8,
Sv48 = 9,
Sv57 = 10,
Sv64 = 11,
],
]
);
pub struct Reg;
impl_csr_read!(Reg, u64, satp, SATP::Register);
impl_csr_write!(Reg, u64, satp, SATP::Register);
pub const SATP: Reg = Reg;
}
pub use mepc::MEPC;
pub use misa::MISA;
pub use mstatus::MSTATUS;
pub use satp::SATP;

View File

@ -28,6 +28,8 @@ cfg_if! {
extern crate kernel_arch_x86_64 as imp;
} else if #[cfg(target_arch = "x86")] {
extern crate kernel_arch_i686 as imp;
} else if #[cfg(target_arch = "riscv64")] {
extern crate kernel_arch_riscv64 as imp;
} else {
compile_error!("Unsupported architecture");
}

View File

@ -92,6 +92,7 @@ fn main() {
"x86" => (),
"x86_64" => build_x86_64(),
"aarch64" => (),
"riscv64" => (),
_ => panic!("Unknown target arch: {:?}", arch),
}
}

View File

@ -122,7 +122,10 @@ struct BusAddressAllocator {
offset_32: u32,
}
#[cfg_attr(any(target_arch = "x86_64", target_arch = "x86"), allow(dead_code))]
#[cfg_attr(
any(target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64"),
allow(dead_code)
)]
impl BusAddressAllocator {
pub fn from_ranges(ranges: &[PciAddressRange]) -> Self {
let mut range_32 = None;

View File

@ -14,6 +14,12 @@ pub mod x86_64;
#[cfg(all(not(feature = "all"), target_arch = "x86_64"))]
pub use x86_64::FixedTables;
// RISC-V 64-bit
#[cfg(any(feature = "all", target_arch = "riscv64"))]
pub mod riscv64;
#[cfg(all(not(feature = "all"), target_arch = "riscv64"))]
pub use riscv64::FixedTables;
#[cfg(feature = "all")]
pub mod any;

View File

@ -0,0 +1,19 @@
use bytemuck::{Pod, Zeroable};
use crate::RawTable;
pub const KERNEL_L3_COUNT: usize = 8;
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct FixedTables {
_dummy: RawTable,
}
impl FixedTables {
pub const fn zeroed() -> Self {
Self {
_dummy: RawTable::zeroed(),
}
}
}

View File

@ -30,6 +30,8 @@ cfg_if! {
const EXPECTED_ELF_MACHINE: u16 = elf::abi::EM_AARCH64;
} else if #[cfg(target_arch = "x86")] {
const EXPECTED_ELF_MACHINE: u16 = elf::abi::EM_386;
} else if #[cfg(target_arch = "riscv64")] {
const EXPECTED_ELF_MACHINE: u16 = elf::abi::EM_RISCV;
}
}

View File

@ -7,10 +7,6 @@ use device_api::{
interrupt::{IpiDeliveryTarget, IpiMessage},
ResetDevice,
};
// use device_api::{
// interrupt::{IpiDeliveryTarget, IpiMessage},
// ResetDevice,
// };
use kernel_arch::{Architecture, ArchitectureImpl};
use libk_mm::table::EntryLevel;
@ -32,7 +28,17 @@ pub mod i686;
#[cfg(any(target_arch = "x86", rust_analyzer))]
pub use i686::{I686 as PlatformImpl, PLATFORM};
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "x86")))]
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
pub mod riscv64;
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
pub use riscv64::{Riscv64 as PlatformImpl, PLATFORM};
#[cfg(not(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv64",
target_arch = "x86"
)))]
compile_error!("Unsupported architecture");
/// Architecture-specific lowest level of page mapping

View File

@ -0,0 +1,59 @@
.macro LOAD_PCREL label, register, symbol
\label: auipc \register, %pcrel_hi(\symbol)
addi \register, \register, %pcrel_lo(\label)
.endm
.section .text.entry
.option norvc
.type __rv64_entry, @function
.global __rv64_entry
__rv64_entry:
// Jump to parking place if hard id is not zero
csrr t0, mhartid
bnez t0, .spin_loop
// Reset translation control
csrw satp, zero
// Zero the .bss
LOAD_PCREL .L00, t0, __bss_start_phys
LOAD_PCREL .L01, t1, __bss_end_phys
1: bgeu t0, t1, 2f
sd zero, (t0)
addi t0, t0, 4
j 1b
2:
// Setup boot stack
LOAD_PCREL .L02, sp, {boot_stack_bottom} + {boot_stack_size} - {kernel_virt_offset}
// Jump to entry
LOAD_PCREL .L03, t0, {entry_mmode_lower} - {kernel_virt_offset}
jr t0
3: wfi
j 3b
.spin_loop:
wfi
j .spin_loop
.size __rv64_entry, . - __rv64_entry
.section .text
.global __rv64_smode_entry
.type __rv64_smode_entry, @function
.p2align 4
__rv64_smode_entry:
// Set up the stack again
LOAD_PCREL .L04, sp, {boot_stack_bottom} + {boot_stack_size}
// Enter kernel proper
LOAD_PCREL .L05, t0, {entry_smode_lower}
jr t0
1: wfi
j 1b
.size __rv64_smode_entry, . - __rv64_smode_entry

View File

@ -0,0 +1,125 @@
use core::arch::global_asm;
use kernel_arch_riscv64::{
mem::{
table::{PageAttributes, PageEntry, PageTable, L1},
KERNEL_VIRT_OFFSET,
},
registers::{MEPC, MSTATUS, SATP},
};
use libk_mm::{address::PhysicalAddress, table::EntryLevel};
use tock_registers::interfaces::{ReadWriteable, Writeable};
const BOOT_STACK_SIZE: usize = 65536;
#[repr(C, align(0x10))]
struct BootStack<const N: usize>([u8; N]);
impl<const N: usize> BootStack<N> {
pub const fn zeroed() -> Self {
Self([0; N])
}
}
#[link_section = ".bss"]
static mut BOOT_STACK: BootStack<BOOT_STACK_SIZE> = BootStack::zeroed();
static mut TABLE: PageTable<L1> = PageTable::zeroed();
unsafe fn long_jump(pc: usize, sp: usize) -> ! {
core::arch::asm!(r#"
mv sp, {sp}
jr {pc}
"#, pc = in(reg) pc, sp = in(reg) sp, options(noreturn));
}
unsafe extern "C" fn __rv64_bsp_smode_entry_lower() -> ! {
// TODO move this to kernel-arch-riscv64, like in other archs
for i in 0..4 {
TABLE[i] = PageEntry::block(
PhysicalAddress::from_usize(i << L1::SHIFT),
PageAttributes::W | PageAttributes::X,
);
}
// TODO magic numbers
// Map kernel
TABLE[450] = PageEntry::block(
PhysicalAddress::from_usize(0x8000_0000),
PageAttributes::W | PageAttributes::X,
);
let address = (&raw const TABLE).addr();
let address = if address >= KERNEL_VIRT_OFFSET {
address - KERNEL_VIRT_OFFSET
} else {
address
};
SATP.modify(SATP::PPN.val((address as u64) >> 12) + SATP::MODE::Sv39);
let stack = (&raw const BOOT_STACK).addr() + KERNEL_VIRT_OFFSET;
let pc = __rv64_bsp_entry_upper as usize + KERNEL_VIRT_OFFSET;
let sp = stack + BOOT_STACK_SIZE;
long_jump(pc, sp)
}
unsafe extern "C" fn __rv64_bsp_entry_upper() -> ! {
loop {}
}
// Drop to S-mode
unsafe extern "C" fn __rv64_bsp_mmode_entry_lower() -> ! {
extern "C" {
fn __rv64_smode_entry() -> !;
}
MSTATUS.modify(
// Mask S-mode interrupts
MSTATUS::SIE::CLEAR
+ MSTATUS::MPIE::CLEAR
// UXLEN=SXLEN=64
+ MSTATUS::UXL.val(2)
+ MSTATUS::SXL.val(2)
// Little endian
+ MSTATUS::UBE::CLEAR
+ MSTATUS::SBE::CLEAR
// Don't trap S-mode VM insns, sret + U-mode wfi
+ MSTATUS::TVM::CLEAR
+ MSTATUS::TW::CLEAR
+ MSTATUS::TSR::CLEAR
// Disable effective privilege modification
+ MSTATUS::MPRV::CLEAR
// Enable S-mode access to U-mode pages
+ MSTATUS::SUM::SET
// Make mret return to S-mode
+ MSTATUS::MPP::S,
);
let entry = __rv64_smode_entry as usize - KERNEL_VIRT_OFFSET;
MEPC.set(entry as u64);
// Modify pmpcfg/pmpaddr to allow lower-level execution
unsafe {
let mut pmpcfg0: u64;
core::arch::asm!("csrr {0}, pmpcfg0", out(reg) pmpcfg0);
let pmpaddr0: u64 = 0xFFFFffffFFFFffff;
pmpcfg0 &= !0xFF;
// A = 1, X, W, R
pmpcfg0 |= 0xF;
core::arch::asm!("csrw pmpaddr0, {0}; csrw pmpcfg0, {1}", in(reg) pmpaddr0, in(reg) pmpcfg0);
}
core::arch::asm!("mret", options(noreturn));
}
global_asm!(
include_str!("entry.S"),
entry_mmode_lower = sym __rv64_bsp_mmode_entry_lower,
entry_smode_lower = sym __rv64_bsp_smode_entry_lower,
boot_stack_bottom = sym BOOT_STACK,
kernel_virt_offset = const KERNEL_VIRT_OFFSET,
boot_stack_size = const BOOT_STACK_SIZE,
);

View File

@ -0,0 +1,48 @@
#![allow(missing_docs)]
use abi::error::Error;
use alloc::sync::Arc;
use device_api::{
interrupt::{IpiDeliveryTarget, IpiMessage},
ResetDevice,
};
use kernel_arch_riscv64::mem::KERNEL_VIRT_OFFSET;
use libk_mm::table::EntryLevel;
use super::Platform;
pub mod boot;
pub struct Riscv64;
#[derive(Debug, Clone, Copy)]
pub struct L3;
impl EntryLevel for L3 {
const SIZE: usize = 4096;
const SHIFT: usize = 12;
}
impl Platform for Riscv64 {
const KERNEL_VIRT_OFFSET: usize = KERNEL_VIRT_OFFSET;
type L3 = L3;
unsafe fn reset(&self) -> ! {
loop {}
}
unsafe fn send_ipi(&self, target: IpiDeliveryTarget, msg: IpiMessage) -> Result<bool, Error> {
let _ = target;
let _ = msg;
loop {}
}
unsafe fn start_application_processors(&self) {
loop {}
}
fn register_reset_device(&self, reset: Arc<dyn ResetDevice>) -> Result<(), Error> {
let _ = reset;
loop {}
}
}
pub static PLATFORM: Riscv64 = Riscv64;

View File

@ -52,6 +52,10 @@ pub const fn arch_str() -> &'static str {
{
"x86_64"
}
#[cfg(target_arch = "riscv64")]
{
"riscv64"
}
#[cfg(target_arch = "x86")]
{
"i686"

View File

@ -9,7 +9,7 @@ use std::{
use clap::Parser;
use elf::{
abi::{EM_386, EM_AARCH64, EM_X86_64, PT_LOAD},
abi::{EM_386, EM_AARCH64, EM_RISCV, EM_X86_64, PT_LOAD},
endian::AnyEndian,
ElfStream,
};
@ -115,6 +115,7 @@ fn find_tables<F: Read + Seek>(elf: &mut ElfStream<AnyEndian, F>) -> Result<(u64
let section_size = match elf.ehdr.e_machine {
EM_AARCH64 => size_of::<memtables::aarch64::FixedTables>(),
EM_X86_64 => size_of::<memtables::x86_64::FixedTables>(),
EM_RISCV => size_of::<memtables::riscv64::FixedTables>(),
_ => unimplemented!(),
};
let (shdrs, strtab) = elf.section_headers_with_strtab()?;
@ -237,6 +238,10 @@ fn build_tables<F: Read + Seek>(
)?
.build()
.map(into_any),
EM_RISCV => {
// TODO
std::process::exit(0);
}
_ => todo!(),
}?;

View File

@ -17,6 +17,11 @@ pub(crate) mod i686;
#[cfg(target_arch = "x86")]
use i686 as arch_impl;
#[cfg(target_arch = "riscv64")]
pub(crate) mod riscv64;
#[cfg(target_arch = "riscv64")]
use riscv64 as arch_impl;
pub use arch_impl::SavedFrame;
pub trait FrameOps {

View File

@ -0,0 +1,19 @@
#![allow(missing_docs)]
use super::FrameOps;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct SavedFrame {}
impl FrameOps for SavedFrame {
fn set_user_ip(&mut self, value: usize) {
let _ = value;
todo!()
}
fn user_ip(&self) -> usize {
todo!()
}
}

View File

@ -10,6 +10,7 @@ use device::{QemuDevice, QemuSerialTarget};
pub mod aarch64;
pub mod i386;
pub mod riscv64;
pub mod x86_64;
pub mod device;
@ -82,6 +83,12 @@ impl Qemu<i386::QemuI386> {
}
}
impl Qemu<riscv64::QemuRiscv64> {
pub fn new_riscv64() -> Self {
Qemu::new(riscv64::QemuRiscv64)
}
}
impl<A: Architecture> Qemu<A> {
pub fn new(arch: A) -> Self {
Self {

60
lib/qemu/src/riscv64.rs Normal file
View File

@ -0,0 +1,60 @@
use std::{path::PathBuf, process::Command};
use crate::{Architecture, IntoArgs};
#[derive(Debug)]
pub enum Cpu {
Rv64,
}
#[derive(Debug)]
pub enum Machine {
Virt,
}
#[derive(Debug)]
pub struct QemuRiscv64;
#[derive(Debug)]
pub struct Image {
pub kernel: PathBuf,
}
impl IntoArgs for Machine {
fn add_args(&self, command: &mut Command) {
command.arg("-M");
match self {
Self::Virt => command.arg("virt"),
};
}
}
impl IntoArgs for Cpu {
fn add_args(&self, command: &mut Command) {
command.arg("-cpu");
match self {
Self::Rv64 => command.arg("rv64,zicsr=true,zifencei=true"),
};
}
}
impl IntoArgs for Image {
fn add_args(&self, command: &mut Command) {
command.arg("-kernel");
command.arg(&self.kernel);
command.args(["-bios", "none"]);
}
}
impl Architecture for QemuRiscv64 {
type CpuType = Cpu;
type ImageType = Image;
type MachineType = Machine;
const DEFAULT_COMMAND: &'static str = "qemu-system-riscv64";
}
impl IntoArgs for QemuRiscv64 {
fn add_args(&self, command: &mut Command) {
let _ = command;
}
}

View File

@ -26,9 +26,14 @@ mod i686;
#[cfg(any(target_arch = "x86", rust_analyzer))]
use i686 as imp;
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
mod riscv64;
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
use riscv64 as imp;
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64", rust_analyzer))]
mod variant1;
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64", rust_analyzer))]
use variant1 as layout;
#[cfg(any(target_arch = "x86", target_arch = "x86_64", rust_analyzer))]

View File

@ -0,0 +1,12 @@
#![allow(missing_docs)]
use abi::error::Error;
pub fn get_thread_pointer() -> usize {
todo!()
}
pub unsafe fn set_thread_pointer(value: usize) -> Result<(), Error> {
let _ = value;
todo!()
}

View File

@ -9,8 +9,11 @@ mod x86_64;
#[cfg(any(target_arch = "x86", rust_analyzer))]
#[macro_use]
mod i686;
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
#[macro_use]
mod riscv64;
#[allow(missing_docs)]
#[allow(missing_docs, unreachable_code)]
mod generated {
// Import all the necessary types from generated ABI
use abi::{

View File

@ -0,0 +1,53 @@
/// 64-bit RISC-V implementations of the syscall macro
#[macro_export]
macro_rules! syscall {
($num:expr $(,)?) => {{
let _ = $num;
todo!()
}};
($num:expr, $a0:expr $(,)?) => {{
let _ = $num;
let _ = $a0;
todo!()
}};
($num:expr, $a0:expr, $a1:expr $(,)?) => {{
let _ = $num;
let _ = $a0;
let _ = $a1;
todo!()
}};
($num:expr, $a0:expr, $a1:expr, $a2:expr $(,)?) => {{
let _ = $num;
let _ = $a0;
let _ = $a1;
let _ = $a2;
todo!()
}};
($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr $(,)?) => {{
let _ = $num;
let _ = $a0;
let _ = $a1;
let _ = $a2;
let _ = $a3;
todo!()
}};
($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr $(,)?) => {{
let _ = $num;
let _ = $a0;
let _ = $a1;
let _ = $a2;
let _ = $a3;
let _ = $a4;
todo!()
}};
($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr $(,)?) => {{
let _ = $num;
let _ = $a0;
let _ = $a1;
let _ = $a2;
let _ = $a3;
let _ = $a4;
let _ = $a5;
todo!()
}};
}

View File

@ -42,6 +42,7 @@ pub struct KernelProcessed(pub KernelBuilt);
pub struct InitrdGenerated(pub PathBuf);
pub struct ImageBuilt(pub PathBuf);
pub enum AllBuilt {
Riscv64(KernelProcessed),
X86_64(ImageBuilt),
AArch64(KernelProcessed, InitrdGenerated),
I686(ImageBuilt),
@ -98,12 +99,16 @@ pub fn build_all(env: &BuildEnv) -> Result<AllBuilt, Error> {
// for module in modules {
// install_extra.push((module.clone(), module.file_name().unwrap().into()));
// }
if env.arch == Arch::riscv64 {
return Ok(AllBuilt::Riscv64(kernel));
}
// Userspace stuff
let initrd = userspace::build_initrd(env, install_extra, check)?;
// Build target-specific image
let image = match env.arch {
Arch::riscv64 => AllBuilt::Riscv64(kernel),
Arch::aarch64 => AllBuilt::AArch64(kernel, initrd),
Arch::x86_64 => AllBuilt::X86_64(x86_64::build_image(env, kernel, initrd)?),
Arch::i686 => AllBuilt::I686(i686::build_image(env, kernel, initrd)?),

View File

@ -56,11 +56,16 @@ fn check_commands_aarch64() -> Result<CommandsOk, Error> {
])
}
fn check_commands_riscv64() -> Result<CommandsOk, Error> {
check_command_list([("ld64.lld", "Install LLVM")])
}
pub fn check_build_env(arch: Arch) -> Result<AllOk, Error> {
let user_toolchain = check_user_toolchain()?;
let commands = match arch {
Arch::x86_64 => check_commands_x86_64()?,
Arch::aarch64 => check_commands_aarch64()?,
Arch::riscv64 => check_commands_riscv64()?,
Arch::i686 => check_commands_i686()?,
};
Ok(AllOk(commands, user_toolchain))

View File

@ -62,6 +62,7 @@ pub enum Profile {
pub enum Arch {
#[default]
aarch64,
riscv64,
x86_64,
i686,
}
@ -127,6 +128,7 @@ impl BuildEnv {
) -> Self {
let kernel_triple = match (arch, board) {
(Arch::aarch64, Board::virt | Board::default) => "aarch64-unknown-qemu",
(Arch::riscv64, Board::virt | Board::default) => "riscv64-unknown-qemu",
(Arch::aarch64, Board::raspi4b) => "aarch64-unknown-raspi4b",
(Arch::x86_64, Board::default) => "x86_64-unknown-none",
(Arch::i686, Board::default) => "i686-unknown-none",
@ -137,6 +139,7 @@ impl BuildEnv {
};
let kernel_linker_script = match arch {
Arch::aarch64 => format!("arm/{kernel_triple}.ld"),
Arch::riscv64 => format!("riscv/{kernel_triple}.ld"),
Arch::i686 | Arch::x86_64 => format!("x86/{kernel_triple}.ld"),
};
let kernel_output_dir =
@ -190,6 +193,7 @@ impl BuildEnv {
impl XTaskConfig {
pub fn components(&self, env: &BuildEnv) -> &BuildComponents {
match env.arch {
Arch::riscv64 => todo!(),
Arch::aarch64 => &self.target.aarch64.components,
Arch::x86_64 => &self.target.x86_64.components,
Arch::i686 => &self.target.i686.components,
@ -217,6 +221,7 @@ impl Arch {
pub fn user_triple(&self) -> &str {
match self {
Self::riscv64 => "riscv64-unknown-yggdrasil",
Self::aarch64 => "aarch64-unknown-yggdrasil",
Self::x86_64 => "x86_64-unknown-yggdrasil",
Self::i686 => "i686-unknown-yggdrasil",
@ -225,6 +230,7 @@ impl Arch {
pub fn name(&self) -> &str {
match self {
Self::riscv64 => "riscv64",
Self::aarch64 => "aarch64",
Self::x86_64 => "x86_64",
Self::i686 => "i686",

View File

@ -7,7 +7,7 @@ use std::{
use qemu::{
aarch64,
device::{QemuDevice, QemuDrive, QemuNic, QemuSerialTarget},
i386, x86_64, Qemu,
i386, riscv64, x86_64, Qemu,
};
use crate::{
@ -254,6 +254,27 @@ fn run_i686(
Ok(qemu.into_command())
}
fn run_riscv64(
config: &QemuConfig,
qemu_bin: Option<PathBuf>,
devices: Vec<QemuDevice>,
kernel: PathBuf,
) -> Result<Command, Error> {
let _ = config;
let _ = devices;
let mut qemu = Qemu::new_riscv64();
if let Some(qemu_bin) = qemu_bin {
qemu.override_qemu(qemu_bin);
}
qemu.with_serial(QemuSerialTarget::MonStdio)
.with_machine(riscv64::Machine::Virt)
.with_cpu(riscv64::Cpu::Rv64)
.with_boot_image(riscv64::Image { kernel });
Ok(qemu.into_command())
}
fn load_qemu_config<P: AsRef<Path>>(path: P) -> Result<QemuConfig, Error> {
let path = path.as_ref();
@ -326,6 +347,9 @@ pub fn run(
add_devices_from_config(&mut devices, disk.as_ref(), &config)?;
let mut command = match built {
AllBuilt::Riscv64(KernelProcessed(KernelBuilt(kernel))) => {
run_riscv64(&config, qemu, devices, kernel)?
}
AllBuilt::AArch64(KernelProcessed(KernelBuilt(kernel)), InitrdGenerated(initrd)) => {
make_kernel_bin(kernel, &kernel_bin)?;
run_aarch64(&config, &env, qemu, devices, kernel_bin, initrd)?