aarch64: add raspberry pi 4b target

This commit is contained in:
Mark Poliakov 2024-12-13 23:35:17 +02:00
parent 8635914ba1
commit 60164fedca
66 changed files with 2459 additions and 1001 deletions

2
Cargo.lock generated
View File

@ -443,8 +443,10 @@ name = "device-tree"
version = "0.1.0"
dependencies = [
"device-api",
"discrete_range_map",
"fdt-rs",
"libk-mm",
"libk-util",
"log",
"yggdrasil-abi",
]

View File

@ -86,3 +86,6 @@ features = ["no_std_stream"]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }
[workspace.lints.clippy]
derivable_impls = { level = "allow" }
# [profile.dev]
# opt-level = "s"

57
doc/raspi4b.txt Normal file
View File

@ -0,0 +1,57 @@
**NOTE** I haven't yet tested direct boot through Raspberry's
proprietary bootloader.
Booting Yggdrasil on Raspberry Pi 4B with u-boot:
1. Clone u-boot sources to some directory and checkout some
stable branch. I've used v2024.10.
2. Modify cmd/boot.c by replacing the do_go_exec function:
/* Allow ports to override the default behavior */
__attribute__((weak))
unsigned long do_go_exec(ulong (*entry)(int, char * const []), int argc,
char *const argv[])
{
void *entry_ptr = (void *) entry;
ulong fdt_addr_r = 0;
if (argc >= 2) {
fdt_addr_r = hextoul(argv[1], NULL);
}
void (*func)(ulong) = entry_ptr;
func(fdt_addr_r);
return 0;
}
3. make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 rpi_4_defconfig
4. make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 -j
5. Copy u-boot.bin into your Pi SD-card's boot partition.
**NOTE** I assume you have all the bootloader parts in the boot partition already.
If not, clone raspberry fw repo and copy the following files to the boot partition:
* bootcode.bin
* start4.elf
* all the .dtb files (a bcm2711-rpi-4-b.dtb should be enough though)
6. config.txt:
enable_uart=1
arm64_bit=1
kernel=u-boot.bin
7. Compile the OS with `cargo xtask --arch=aarch64 --board=raspi4b --release`
8. Copy the following files into some directory:
* target/aarch64-unknown-raspi4b/release/yggdrasil-kernel
* userspace/target/aarch64-unknown-yggdrasil/release/initrd.tar
9. cd into that directory and start a TFTP server of your choice. I used `uftpd`.
10. Connect an ethernet and serial to the Pi and run the following commands in u-boot shell:
tftpboot 0x04000000 <YOUR IP>:initrd.tar
tftpboot ${loadaddr} <YOUR IP>:yggdrasil-kernel
load mmc 0:1 ${fdt_addr_r} bcm2711-rpi-4-b.dtb
fdt addr ${fdt_addr_r}
fdt resize
fdt memory 0x0 0x3C000000
fdt chosen 0x04000000 <WHATEVER SIZE WAS PRINTED WHEN RUNNING THE FIRST COMMAND>
bootelf -p
go ${kernel_addr_r} ${fdt_addr_r}
11. Yggdrasil OS should start!

View File

@ -0,0 +1,20 @@
{
"is-builtin": false,
"arch": "aarch64",
"os": "none",
"llvm-target": "aarch64-unknown-none",
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
"max-atomic-width": 128,
"target-pointer-width": "64",
"features": "+v8a,+strict-align,-neon,-fp-armv8",
"disable-redzone": true,
"executables": true,
"panic-strategy": "abort",
"dynamic-linking": true,
"relocation-model": "pic",
"eh-frame-header": false,
"linker": "rust-lld",
"linker-flavor": "ld.lld"
}

BIN
etc/dtb/bcm2711-rpi-4-b.dtb Normal file

Binary file not shown.

View File

@ -21,12 +21,13 @@ SECTIONS {
. = ALIGN(4K);
.rodata : AT(. - KERNEL_VIRT_OFFSET) {
*(.eh_frame*)
. = ALIGN(16);
PROVIDE(__dt_probes_start = .);
KEEP(*(.dt_probes));
PROVIDE(__dt_probes_end = .);
*(.rodata*)
*(.eh_frame*)
. = ALIGN(16);
PROVIDE(__init_array_start = .);
KEEP(*(.init_array*))
PROVIDE(__init_array_end = .);
}
. = ALIGN(4K);
@ -48,6 +49,7 @@ SECTIONS {
}
. = 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,55 @@
ENTRY(__aarch64_entry);
KERNEL_PHYS_BASE = 0x80000;
KERNEL_VIRT_OFFSET = 0xFFFFFF8000000000;
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*)
*(.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

@ -85,5 +85,9 @@ kernel-arch-aarch64.workspace = true
default = ["fb_console"]
fb_console = []
# TODO replace this with a better configuration mechanism
aarch64_board_virt = ["kernel-arch-aarch64/aarch64_board_virt"]
aarch64_board_raspi4b = ["kernel-arch-aarch64/aarch64_board_raspi4b"]
[lints]
workspace = true

View File

@ -17,3 +17,11 @@ tock-registers.workspace = true
[build-dependencies]
cc = "1.0"
[features]
default = []
aarch64_board_virt = []
aarch64_board_raspi4b = []
[lints]
workspace = true

View File

@ -23,6 +23,8 @@
.endm
.macro LOAD_TASK_STATE
dsb ishst
// x19 == tpidr_el0, x20 = ttbr0_el1
ldp x19, x20, [sp, #16 * 6]
msr tpidr_el0, x19
@ -36,6 +38,12 @@
ldp x29, x30, [sp, #16 * 5]
add sp, sp, #{context_size}
isb sy
tlbi vmalle1is
ic iallu
dsb ish
isb sy
.endm
__aarch64_task_enter_kernel:
@ -87,7 +95,7 @@ __aarch64_task_enter_user:
mov lr, xzr
dmb ish
dsb ish
isb sy
eret

View File

@ -1,5 +1,5 @@
#![no_std]
#![feature(naked_functions, trait_upcasting)]
#![feature(naked_functions, trait_upcasting, decl_macro)]
#![allow(clippy::new_without_default)]
extern crate alloc;
@ -64,6 +64,7 @@ impl Architecture for ArchitectureImpl {
DAIF.read(DAIF::I) != 0
}
#[inline(never)]
unsafe fn set_interrupt_mask(mask: bool) -> bool {
let old = Self::interrupt_mask();
if mask {
@ -125,7 +126,7 @@ impl Architecture for ArchitectureImpl {
}
fn local_interrupt_controller() -> Option<&'static dyn LocalInterruptController> {
todo!()
None
}
// fn local_interrupt_controller() -> Option<&'static dyn LocalInterruptController> {

View File

@ -1,16 +1,16 @@
use core::{
alloc::Layout,
ops::{Deref, DerefMut},
ptr::addr_of,
sync::atomic::AtomicUsize,
sync::atomic::Ordering,
sync::atomic::{self, AtomicUsize, Ordering},
};
use aarch64_cpu::registers::{TTBR0_EL1, TTBR1_EL1};
use aarch64_cpu::{
asm::barrier,
registers::{PAR_EL1, TTBR0_EL1, TTBR1_EL1},
};
use kernel_arch_interface::{
mem::{DeviceMemoryAttributes, KernelTableManager, RawDeviceMemoryMapping},
sync::split_spinlock,
KERNEL_VIRT_OFFSET,
split_spinlock, Architecture, KERNEL_VIRT_OFFSET,
};
use libk_mm_interface::{
address::PhysicalAddress,
@ -18,9 +18,11 @@ use libk_mm_interface::{
};
use memtables::aarch64::{FixedTables, KERNEL_L3_COUNT};
use static_assertions::const_assert_eq;
use tock_registers::interfaces::Writeable;
use tock_registers::interfaces::{Readable, Writeable};
use yggdrasil_abi::error::Error;
use crate::ArchitectureImpl;
use self::table::{PageAttributes, PageEntry, PageTable, L1, L2, L3};
pub mod process;
@ -31,7 +33,11 @@ pub struct KernelTableManagerImpl;
// TODO eliminate this requirement by using precomputed indices
const MAPPING_OFFSET: usize = KERNEL_VIRT_OFFSET;
#[cfg(any(feature = "aarch64_board_virt", rust_analyzer))]
const KERNEL_PHYS_BASE: usize = 0x40080000;
#[cfg(any(feature = "aarch64_board_raspi4b", rust_analyzer))]
const KERNEL_PHYS_BASE: usize = 0x80000;
// Precomputed mappings
const KERNEL_L1_INDEX: usize = page_index::<L1>(KERNEL_VIRT_OFFSET + KERNEL_PHYS_BASE);
@ -41,6 +47,9 @@ const KERNEL_END_L2_INDEX: usize = KERNEL_START_L2_INDEX + KERNEL_L3_COUNT;
// Must not be zero, should be at 4MiB
const_assert_eq!(KERNEL_START_L2_INDEX, 0);
// From static mapping
#[cfg(any(feature = "aarch64_board_raspi4b", rust_analyzer))]
const_assert_eq!(KERNEL_L1_INDEX, 0);
#[cfg(any(feature = "aarch64_board_virt", rust_analyzer))]
const_assert_eq!(KERNEL_L1_INDEX, 1);
// Runtime mappings
@ -72,7 +81,7 @@ split_spinlock! {
use libk_mm_interface::KernelImageObject;
#[link_section = ".data.tables"]
static KERNEL_TABLES @ inner<lock: ArchitectureImpl>: KernelImageObject<FixedTables> =
static KERNEL_TABLES: KernelImageObject<FixedTables> =
unsafe { KernelImageObject::new(FixedTables::zeroed()) };
}
@ -175,7 +184,7 @@ fn ram_block_flags() -> PageAttributes {
// TODO UXN, PXN
PageAttributes::BLOCK
| PageAttributes::ACCESS
| PageAttributes::SH_INNER
| PageAttributes::SH_OUTER
| PageAttributes::PAGE_ATTR_NORMAL
| PageAttributes::PRESENT
}
@ -199,6 +208,7 @@ unsafe fn map_early_pages(physical: PhysicalAddress, count: usize) -> Result<usi
let page = physical.add(i * L3::SIZE);
// TODO NX, NC
EARLY_MAPPING_L3[i + l3i] = PageEntry::normal_page(page, PageAttributes::empty());
tlb_flush_vaae1(EARLY_MAPPING_OFFSET + (l3i + i) * L3::SIZE);
}
return Ok(EARLY_MAPPING_OFFSET + l3i * L3::SIZE);
@ -216,8 +226,6 @@ unsafe fn unmap_early_page(address: usize) {
assert!(EARLY_MAPPING_L3[l3i].is_present());
EARLY_MAPPING_L3[l3i] = PageEntry::INVALID;
// TODO invalidate tlb
}
/// # Safety
@ -225,13 +233,17 @@ unsafe fn unmap_early_page(address: usize) {
/// Only meant to be used by the architecture initialization functions.
pub unsafe fn map_ram_l1(index: usize) {
if index >= RAM_MAPPING_L1_COUNT {
todo!()
ArchitectureImpl::halt();
}
let mut tables = KERNEL_TABLES.lock();
assert_eq!(tables.l1.data[index + RAM_MAPPING_START_L1I], 0);
let table_index = index + RAM_MAPPING_START_L1I;
tables.l1.data[index + RAM_MAPPING_START_L1I] =
((index * L1::SIZE) as u64) | ram_block_flags().bits();
if tables.l1.data[table_index] != 0 {
ArchitectureImpl::halt();
}
tables.l1.data[table_index] = ((index * L1::SIZE) as u64) | ram_block_flags().bits();
tlb_flush_vaae1(RAM_MAPPING_OFFSET + index * L1::SIZE);
}
// Device mappings
@ -258,6 +270,7 @@ unsafe fn map_device_memory_l3(
// TODO NX, NC
DEVICE_MAPPING_L3S[l2i][l3i] = PageEntry::device_page(base.add(j * L3::SIZE));
tlb_flush_vaae1(DEVICE_MAPPING_OFFSET + l2i * L2::SIZE + l3i * L3::SIZE);
}
return Ok(DEVICE_MAPPING_OFFSET + i * L3::SIZE);
@ -266,6 +279,7 @@ unsafe fn map_device_memory_l3(
Err(Error::OutOfMemory)
}
#[allow(unused)]
unsafe fn map_device_memory_l2(
base: PhysicalAddress,
count: usize,
@ -280,6 +294,7 @@ unsafe fn map_device_memory_l2(
for j in 0..count {
DEVICE_MAPPING_L2[i + j] = PageEntry::<L2>::device_block(base.add(j * L2::SIZE));
tlb_flush_vaae1(DEVICE_MAPPING_OFFSET + (i + j) * L2::SIZE);
}
// log::debug!(
@ -288,6 +303,7 @@ unsafe fn map_device_memory_l2(
// count,
// DEVICE_MAPPING_OFFSET + i * L2::SIZE
// );
return Ok(DEVICE_MAPPING_OFFSET + i * L2::SIZE);
}
@ -304,20 +320,21 @@ pub(crate) unsafe fn map_device_memory(
let page_count = (l3_offset + size).page_count::<L3>();
if page_count > 256 {
// Large mapping, use L2 mapping instead
let l2_aligned = base.page_align_down::<L2>();
let l2_offset = base.page_offset::<L2>();
let page_count = (l2_offset + size).page_count::<L2>();
ArchitectureImpl::halt();
// // Large mapping, use L2 mapping instead
// let l2_aligned = base.page_align_down::<L2>();
// let l2_offset = base.page_offset::<L2>();
// let page_count = (l2_offset + size).page_count::<L2>();
let base_address = map_device_memory_l2(l2_aligned, page_count, attrs)?;
let address = base_address + l2_offset;
// let base_address = map_device_memory_l2(l2_aligned, page_count, attrs)?;
// let address = base_address + l2_offset;
Ok(RawDeviceMemoryMapping::from_raw_parts(
address,
base_address,
page_count,
L2::SIZE,
))
// Ok(RawDeviceMemoryMapping::from_raw_parts(
// address,
// base_address,
// page_count,
// L2::SIZE,
// ))
} else {
// Just map the pages directly
let base_address = map_device_memory_l3(l3_aligned, page_count, attrs)?;
@ -351,10 +368,90 @@ pub(crate) unsafe fn unmap_device_memory(map: &RawDeviceMemoryMapping<KernelTabl
}
#[inline]
pub fn tlb_flush_vaae1(mut page: usize) {
page >>= 12;
pub fn tlb_flush_asid(asid: u8) {
barrier::dsb(barrier::ISHST);
let value = (asid as u64) << 48;
unsafe {
core::arch::asm!("tlbi vaae1, {page}", page = in(reg) page);
core::arch::asm!("tlbi aside1, {value}", value = in(reg) value);
}
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
}
#[inline]
pub fn tlb_flush_all() {
barrier::dsb(barrier::ISHST);
unsafe {
core::arch::asm!("tlbi vmalle1is");
}
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
}
#[inline]
pub fn tlb_flush_vaae1(page: usize) {
barrier::dsb(barrier::ISHST);
let argument = page >> 12;
unsafe {
core::arch::asm!("tlbi vaae1, {argument}", argument = in(reg) argument);
}
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
}
pub fn at_s1e0r(input: usize) -> Option<u64> {
barrier::dsb(barrier::ISHST);
unsafe {
core::arch::asm!("at s1e0r, {address}", address = in(reg) input);
}
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
if PAR_EL1.matches_all(PAR_EL1::F::TranslationSuccessfull) {
Some(PAR_EL1.read(PAR_EL1::PA))
} else {
None
}
}
pub fn at_s1e1r(input: usize) -> Option<u64> {
barrier::dsb(barrier::ISHST);
unsafe {
core::arch::asm!("at s1e1r, {address}", address = in(reg) input);
}
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
if PAR_EL1.matches_all(PAR_EL1::F::TranslationSuccessfull) {
Some(PAR_EL1.read(PAR_EL1::PA))
} else {
None
}
}
pub fn ic_iallu() {
atomic::compiler_fence(Ordering::SeqCst);
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
unsafe {
core::arch::asm!("ic iallu");
}
barrier::isb(barrier::SY);
}
pub fn dc_cvac(input: usize) {
barrier::dsb(barrier::ISHST);
unsafe {
core::arch::asm!("dc cvac, {address}", address = in(reg) input);
}
}
fn auto_address<T>(value: *const T) -> usize {
let addr = value.addr();
if addr < KERNEL_VIRT_OFFSET {
// Called from lower half
addr
} else {
// Called from higher-half
addr - KERNEL_VIRT_OFFSET
}
}
@ -364,7 +461,8 @@ pub fn tlb_flush_vaae1(mut page: usize) {
///
/// Unsafe, must only be called by BSP during its early init while still in "lower-half"
pub unsafe fn load_fixed_tables() {
let ttbr0 = KERNEL_TABLES.lock().l1.data.as_ptr().addr() as u64;
let ttbr0 = auto_address(&raw const KERNEL_TABLES) as u64;
TTBR0_EL1.set(ttbr0);
TTBR1_EL1.set(ttbr0);
}
@ -376,9 +474,9 @@ pub unsafe fn load_fixed_tables() {
/// Unsafe, must only be called by BSP during its early init, must already be in "higher-half"
pub unsafe fn init_fixed_tables() {
// TODO this could be built in compile-time too?
let mut tables = KERNEL_TABLES.lock();
let early_mapping_l3_phys = addr_of!(EARLY_MAPPING_L3) as usize - KERNEL_VIRT_OFFSET;
let device_mapping_l2_phys = addr_of!(DEVICE_MAPPING_L2) as usize - KERNEL_VIRT_OFFSET;
let mut tables = KERNEL_TABLES.grab();
let early_mapping_l3_phys = auto_address(&raw const EARLY_MAPPING_L3);
let device_mapping_l2_phys = auto_address(&raw const DEVICE_MAPPING_L2);
for i in 0..DEVICE_MAPPING_L3_COUNT {
let device_mapping_l3_phys = PhysicalAddress::from_usize(
@ -390,8 +488,11 @@ pub unsafe fn init_fixed_tables() {
assert_eq!(tables.l2.data[EARLY_MAPPING_L2I], 0);
tables.l2.data[EARLY_MAPPING_L2I] =
(early_mapping_l3_phys as u64) | kernel_table_flags().bits();
tlb_flush_vaae1(EARLY_MAPPING_OFFSET);
assert_eq!(tables.l1.data[DEVICE_MAPPING_L1I], 0);
tables.l1.data[DEVICE_MAPPING_L1I] =
(device_mapping_l2_phys as u64) | kernel_table_flags().bits();
tlb_flush_all();
}

View File

@ -17,8 +17,9 @@ use yggdrasil_abi::error::Error;
use crate::{mem::table::PageEntry, KernelTableManagerImpl};
use super::{
table::{PageTable, L1, L2, L3},
tlb_flush_vaae1,
dc_cvac, ic_iallu,
table::{PageAttributes, PageTable, L1, L2, L3},
tlb_flush_asid, tlb_flush_vaae1,
};
/// AArch64 implementation of a process address space table
@ -49,6 +50,8 @@ impl<TA: TableAllocator> ProcessAddressSpaceManager<TA> for ProcessAddressSpaceI
l1[i] = PageEntry::INVALID;
}
tlb_flush_asid(asid);
Ok(Self {
l1,
asid,
@ -68,7 +71,10 @@ impl<TA: TableAllocator> ProcessAddressSpaceManager<TA> for ProcessAddressSpaceI
) -> Result<(), Error> {
self.write_l3_entry(
address,
PageEntry::normal_page(physical, flags.into()),
PageEntry::normal_page(
physical,
PageAttributes::from(flags) | PageAttributes::NON_GLOBAL,
),
false,
)
}
@ -78,7 +84,7 @@ impl<TA: TableAllocator> ProcessAddressSpaceManager<TA> for ProcessAddressSpaceI
}
fn as_address_with_asid(&self) -> u64 {
unsafe { u64::from(self.l1.as_physical_address()) | ((self.asid as u64) << 48) }
unsafe { u64::from(self.l1.as_physical_address()) | ((self.asid as u64) << 48) | 1 }
}
unsafe fn clear(&mut self) {
@ -107,6 +113,7 @@ impl<TA: TableAllocator> ProcessAddressSpaceImpl<TA> {
}
l3[l3i] = entry;
dc_cvac((&raw const l3[l3i]).addr());
tlb_flush_vaae1(virt);
Ok(())
@ -124,6 +131,8 @@ impl<TA: TableAllocator> ProcessAddressSpaceImpl<TA> {
let page = l3[l3i].as_page().ok_or(Error::DoesNotExist)?;
l3[l3i] = PageEntry::INVALID;
ic_iallu();
dc_cvac((&raw const l3[l3i]).addr());
tlb_flush_vaae1(virt);
Ok(page)

View File

@ -1,9 +1,11 @@
use core::{
fmt,
marker::PhantomData,
ops::{Index, IndexMut, Range},
};
use bitflags::bitflags;
use kernel_arch_interface::KERNEL_VIRT_OFFSET;
use libk_mm_interface::{
address::{AsPhysicalAddress, PhysicalAddress},
pointer::{PhysicalRef, PhysicalRefMut},
@ -16,6 +18,8 @@ use yggdrasil_abi::error::Error;
use crate::KernelTableManagerImpl;
use super::dc_cvac;
bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PageAttributes: u64 {
@ -62,6 +66,13 @@ pub struct L2;
#[derive(Clone, Copy)]
pub struct L3;
#[derive(Debug, Clone, Copy)]
pub enum EntryType {
Table(PhysicalAddress),
Page(PhysicalAddress),
Invalid,
}
impl NonTerminalEntryLevel for L1 {
type NextLevel = L2;
}
@ -101,6 +112,26 @@ impl<L: EntryLevel> PageTable<L> {
Ok(table)
}
/// Creates a reference to [PageTable] from a physical address.
///
/// # Safety
///
/// The function takes in a raw physical address.
pub unsafe fn from_physical(
physical: PhysicalAddress,
) -> Option<PhysicalRefMut<'static, Self, KernelTableManagerImpl>> {
if physical.into_usize() >= KERNEL_VIRT_OFFSET {
// Looks fishy
return None;
}
if !physical.is_aligned_for::<L3>() {
return None;
}
let inner = PhysicalRefMut::map(physical);
Some(inner)
}
}
impl<L: EntryLevel> PageEntry<L> {
@ -115,6 +146,12 @@ impl<L: EntryLevel> PageEntry<L> {
}
}
impl<L: NonTerminalEntryLevel> PageTable<L> {
pub fn walk(&self, index: usize) -> EntryType {
self[index].classify()
}
}
impl<L: NonTerminalEntryLevel + 'static> NextPageTable for PageTable<L> {
type NextLevel = PageTable<L::NextLevel>;
type TableRef = PhysicalRef<'static, PageTable<L::NextLevel>, KernelTableManagerImpl>;
@ -146,6 +183,7 @@ impl<L: NonTerminalEntryLevel + 'static> NextPageTable for PageTable<L> {
unsafe { table.as_physical_address() },
PageAttributes::empty(),
);
dc_cvac((&raw const self[index]).addr());
Ok(table)
}
}
@ -184,6 +222,7 @@ where
}
self[index] = PageEntry::INVALID;
dc_cvac((&raw const self[index]).addr());
}
}
}
@ -202,7 +241,7 @@ impl<L: NonTerminalEntryLevel> PageEntry<L> {
| (PageAttributes::BLOCK
| PageAttributes::PRESENT
| PageAttributes::ACCESS
| PageAttributes::SH_INNER
| PageAttributes::SH_OUTER
| PageAttributes::PAGE_ATTR_NORMAL
| attrs)
.bits(),
@ -236,6 +275,16 @@ impl<L: NonTerminalEntryLevel> PageEntry<L> {
None
}
}
pub fn classify(self) -> EntryType {
if !self.is_present() {
EntryType::Invalid
} else if let Some(table) = self.as_table() {
EntryType::Table(table)
} else {
EntryType::Page(PhysicalAddress::from_u64(self.0 & !0xFFF))
}
}
}
impl PageEntry<L3> {
@ -260,9 +309,7 @@ impl PageEntry<L3> {
| PageAttributes::PRESENT
| PageAttributes::ACCESS
| PageAttributes::SH_OUTER
| PageAttributes::PAGE_ATTR_DEVICE
| PageAttributes::UXN
| PageAttributes::PXN)
| PageAttributes::PAGE_ATTR_DEVICE)
.bits(),
PhantomData,
)
@ -336,3 +383,13 @@ impl From<PageAttributes> for MapAttributes {
out
}
}
impl fmt::Display for EntryType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Table(address) => write!(f, "table @ {address:#x}"),
Self::Page(address) => write!(f, "page @ {address:#x}"),
Self::Invalid => f.write_str("<invalid>"),
}
}
}

View File

@ -9,6 +9,9 @@ use task::Scheduler;
extern crate alloc;
#[macro_use]
pub mod macros;
pub mod cpu;
pub mod guard;
pub mod mem;

View File

@ -0,0 +1,92 @@
/// Helper macro to implement "split" locks. This may be needed when a very specific storage
/// layout for the locked type is required.
// pub macro split_spinlock(
// ) {
#[macro_export]
macro_rules! split_spinlock {
(
$(use $use:path;)*
$(#[$meta:meta])*
static $name:ident: $ty:ty = $init:expr;
) => {
pub use $name::$name;
#[allow(non_snake_case)]
pub mod $name {
$(use $use;)*
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicU32, Ordering};
#[repr(transparent)]
pub struct __Wrapper {
inner: UnsafeCell<$ty>
}
$(#[$meta])*
pub static $name: __Wrapper = __Wrapper {
inner: UnsafeCell::new($init)
};
static __LOCK: AtomicU32 = AtomicU32::new(0);
pub struct __Guard($crate::guard::IrqGuard<ArchitectureImpl>);
pub struct __UnsafeGuard($crate::guard::IrqGuard<ArchitectureImpl>);
impl __Wrapper {
#[inline(never)]
pub fn lock(&self) -> __Guard {
let irq = $crate::guard::IrqGuard::acquire();
while __LOCK.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed).is_err() {
core::hint::spin_loop();
}
__Guard(irq)
}
#[inline(never)]
pub unsafe fn grab(&self) -> __UnsafeGuard {
let irq = $crate::guard::IrqGuard::acquire();
__UnsafeGuard(irq)
}
}
unsafe impl Sync for __Wrapper {}
impl core::ops::Deref for __Guard {
type Target = $ty;
fn deref(&self) -> &Self::Target {
unsafe { &*$name.inner.get() }
}
}
impl core::ops::DerefMut for __Guard {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *$name.inner.get() }
}
}
impl core::ops::Deref for __UnsafeGuard {
type Target = $ty;
fn deref(&self) -> &Self::Target {
unsafe { &*$name.inner.get() }
}
}
impl core::ops::DerefMut for __UnsafeGuard {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *$name.inner.get() }
}
}
impl Drop for __Guard {
fn drop(&mut self) {
__LOCK.store(0, Ordering::Release)
}
}
}
};
}

View File

@ -154,71 +154,6 @@ impl<A: Architecture, T> DerefMut for IrqSafeSpinlockGuard<'_, A, T> {
}
}
/// Helper macro to implement "split" locks. This may be needed when a very specific storage
/// layout for the locked type is required.
pub macro split_spinlock(
$(use $use:path;)*
$(#[$meta:meta])*
static $name:ident @ $field:ident <$lock:ident: $arch:ty>: $ty:ty = $init:expr;
) {
pub use $name::$name;
#[allow(non_snake_case)]
pub mod $name {
$(use $use;)*
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
#[repr(transparent)]
pub struct __Wrapper {
$field: UnsafeCell<$ty>
}
$(#[$meta])*
pub static $name: __Wrapper = __Wrapper {
$field: UnsafeCell::new($init)
};
static __LOCK: AtomicBool = AtomicBool::new(false);
pub struct __Guard($crate::guard::IrqGuard<$arch>);
impl __Wrapper {
pub fn $lock(&self) -> __Guard {
let irq = $crate::guard::IrqGuard::acquire();
while __LOCK.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
core::hint::spin_loop();
}
__Guard(irq)
}
}
unsafe impl Sync for __Wrapper {}
impl core::ops::Deref for __Guard {
type Target = $ty;
fn deref(&self) -> &Self::Target {
unsafe { &*$name.$field.get() }
}
}
impl core::ops::DerefMut for __Guard {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *$name.$field.get() }
}
}
impl Drop for __Guard {
fn drop(&mut self) {
__LOCK.store(false, Ordering::Release)
}
}
}
}
static LOCK_HACK: AtomicBool = AtomicBool::new(false);
/// "Hacks" all the locks in the kernel to make them function as "NULL"-locks instead of spinlocks.

View File

@ -0,0 +1,19 @@
//! Bus device interfaces
use core::ops::Range;
use alloc::sync::Arc;
use yggdrasil_abi::error::Error;
use crate::device::Device;
/// Bus device which provides an interconnect between two or more address spaces.
///
/// NOTE: only memory-to-memory busses are supported via this trait as of yet.
pub trait Bus: Device {
/// Translates a range of bus addresses into the addresses represented in host memory.
///
/// NOTE: nested busses with bus1 -> bus0 -> host translation schemes are not yet supported.
fn map_range(&self, bus_range: Range<u64>) -> Result<Range<u64>, Error>;
fn enumerate(&self, handler: fn(Arc<dyn Device>));
}

View File

@ -4,6 +4,7 @@
extern crate alloc;
pub mod bus;
pub mod device;
pub mod interrupt;
pub mod serial;

View File

@ -9,6 +9,11 @@ edition = "2021"
yggdrasil-abi.workspace = true
device-api = { path = "../device-api", features = ["derive"] }
libk-mm.workspace = true
libk-util.workspace = true
fdt-rs.workspace = true
log.workspace = true
discrete_range_map.workspace = true
[lints]
workspace = true

View File

@ -0,0 +1,45 @@
/dts-v1/;
/ {
compatible = "vendor,board-v1", "vendor,soc-v1";
model = "My Example Board v1";
#address-cells = <0x02>;
#size-cells = <0x01>;
interrupt-parent = <0x01>;
alias {
serial = "/soc/serial@10000000";
};
chosen {
stdout-path = "serial:115200n8";
};
soc {
compatible = "simple-bus";
#address-cells = <0x01>;
#size-cells = <0x01>;
serial@10000000 {
compatible = "vendor,soc-v1-uart";
reg = <0x10000000 0x200>;
interrupts = <0x01 0x64 0x00>;
clocks = <0x02 0x00>;
};
interrupt-controller@10001000 {
compatible = "vendor,soc-v1-intc";
interrupt-controller;
#interrupt-cells = <0x03>;
reg = <0x10001000 0x1000 0x10002000 0x2000 0x10004000 0x2000 0x10006000 0x2000>;
phandle = <0x01>;
};
clock@10010000 {
compatible = "vendor,soc-v1-clocks";
#clock-cells = <0x01>;
reg = <0x10010000 0x100>;
phandle = <0x02>;
};
};
};

Binary file not shown.

View File

@ -0,0 +1,48 @@
/dts-v1/;
/ {
compatible = "vendor,board-v1", "vendor,soc-v1";
model = "My Example Board v1";
#address-cells = <2>;
#size-cells = <1>;
interrupt-parent = <&intc>;
aliases {
serial = &uart;
};
chosen {
stdout-path = "serial:115200n8";
};
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
uart: serial@10000000 {
compatible = "vendor,soc-v1-uart";
reg = <0x10000000 0x200>;
interrupts = <0x01 100 0x00>;
clocks = <&clocks 0x00>;
};
intc: interrupt-controller@10001000 {
compatible = "vendor,soc-v1-intc";
interrupt-controller;
#interrupt-cells = <3>;
reg = <0x10001000 0x1000>,
<0x10002000 0x2000>,
<0x10004000 0x2000>,
<0x10006000 0x2000>;
};
clocks: clock@10010000 {
compatible = "vendor,soc-v1-clocks";
#clock-cells = <1>;
reg = <0x10010000 0x100>;
};
};
};

View File

@ -1,144 +1,259 @@
//! Device tree-based driver definitions
use core::mem::size_of;
use core::ops::Range;
use alloc::sync::Arc;
use device_api::device::Device;
use fdt_rs::index::DevTreeIndexNode;
use alloc::{sync::Arc, vec::Vec};
use device_api::{bus::Bus, device::Device};
use discrete_range_map::{DiscreteRangeMap, InclusiveInterval};
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
use yggdrasil_abi::error::Error;
use crate::dt::{DevTreeIndexNodePropGet, DevTreeNodeInfo};
use crate::dt::{
DevTreeIndexNodeExt, DevTreeIndexNodePropGet, DevTreeIndexPropExt, DeviceTree, TNode, TProp,
};
/// Helper macro to return the count of expressions supplied to it
#[macro_export]
macro_rules! count {
() => (0usize);
($x:tt $($xs:tt)*) => (1usize + $crate::count!($($xs)*));
}
pub macro device_tree_driver(
compatible: [$($compatible:expr),+ $(,)?],
probe($node:ident) => $probe_body:block
) {
#[used]
#[link_section = ".init_array"]
static __FN_POINTER: extern "C" fn() = __register_fn;
/// Registers a device driver for compatible device tree nodes
///
/// # Usage example
///
/// ```
/// device_tree_driver! {
/// compatible: ["arm,pl011"],
/// probe(of) => {
/// let my_device = ...; // ... extract some info about the device ...
/// Some(Box::new(my_device))
/// }
/// }
/// ```
#[macro_export]
macro_rules! device_tree_driver {
(
compatible: [$($compatible:literal),+],
probe ($node:ident) => $probe_body:block $(,)?
) => {
const __COMPATIBLE_LEN: usize = $crate::count!($($compatible )+);
static __COMPATIBLE: [&str; __COMPATIBLE_LEN] = [$($compatible),+];
fn __probe_fn($node: &$crate::driver::Node) -> Option<alloc::sync::Arc<dyn device_api::device::Device>>
$probe_body
fn __probe($node: &$crate::dt::DevTreeNodeInfo) ->
Option<alloc::sync::Arc<dyn device_api::device::Device>> $probe_body
core::arch::global_asm!(r#"
.pushsection .dt_probes, "a"
.quad {compatible}
.quad {compatible_len}
.quad {probe_func}
.popsection
"#,
compatible = sym __COMPATIBLE,
compatible_len = const __COMPATIBLE_LEN,
probe_func = sym __probe
);
};
}
struct DevTreeProbe<'a> {
compatible: &'static [&'static str],
probe_func: fn(&'a DevTreeNodeInfo<'a, 'a, 'a>) -> Option<Arc<dyn Device>>,
}
fn iter_dt_probes<'a>() -> impl Iterator<Item = DevTreeProbe<'a>> {
extern "C" {
static __dt_probes_start: u64;
static __dt_probes_end: u64;
#[inline(never)]
extern "C" fn __register_fn() {
$crate::driver::register_driver($crate::driver::Driver {
compatible: &[$($compatible),+],
probe_fn: __probe_fn
});
}
}
unsafe {
let base = &__dt_probes_start as *const u64;
let end = &__dt_probes_end as *const u64;
let len = (end as usize - base as usize) / (size_of::<u64>() * 3);
pub struct Driver {
pub compatible: &'static [&'static str],
pub probe_fn: fn(&Node) -> Option<Arc<dyn Device>>,
}
(0..len).map(move |i| {
let compatible_ptr = *base.add(i * 3);
let compatible_len = *base.add(i * 3 + 1);
let probe_func_ptr = *base.add(i * 3 + 2);
#[derive(Clone)]
pub struct Node {
compatible: Option<&'static str>,
node: TNode<'static>,
}
let compatible =
core::slice::from_raw_parts(compatible_ptr as *const &str, compatible_len as usize);
let probe_func = core::mem::transmute(probe_func_ptr);
DevTreeProbe {
compatible,
probe_func,
impl Node {
pub fn probe(&self) -> Option<Arc<dyn Device>> {
// Then probe the node itself
let compatible = self.compatible?;
let drivers = DRIVERS.read();
for driver in drivers.iter() {
if driver.compatible.contains(&compatible)
&& let Some(device) = (driver.probe_fn)(self)
{
return Some(device);
}
})
}
None
}
pub fn probe_recursive(&self) -> Option<Arc<dyn Device>> {
// Make sure all parents are probed first
if let Some(parent) = self.node.parent() {
let node = from_dt_node(parent);
node.probe_recursive()
} else {
None
};
self.probe()
}
pub fn bus_range(&self, index: usize) -> Option<Range<u64>> {
let reg = self.property("reg")?;
let address_cells = self.parent_address_cells();
let size_cells = self.parent_size_cells();
let (base, len) = reg.read_cells(index, (address_cells, size_cells))?;
Some(base..base + len)
}
pub fn mapped_range(&self, index: usize) -> Option<Range<u64>> {
let bus_range = self.bus_range(index)?;
if self.is_parent_root() {
return Some(bus_range);
}
map_bus_range(bus_range)
}
pub fn parent_address_cells(&self) -> usize {
self.node.address_cells()
}
pub fn parent_size_cells(&self) -> usize {
self.node.size_cells()
}
pub fn self_address_cells(&self) -> Option<usize> {
self.property_val::<u32>("#address-cells")
.map(|val| val as usize)
}
pub fn self_size_cells(&self) -> Option<usize> {
self.property_val::<u32>("#size-cells")
.map(|val| val as usize)
}
pub fn property(&self, name: &str) -> Option<TProp<'static>> {
crate::find_prop(&self.node, name)
}
pub fn property_val<T>(&self, name: &str) -> Option<T>
where
TNode<'static>: DevTreeIndexNodePropGet<T>,
{
self.node.prop(name)
}
pub fn name(&self) -> &str {
self.node.name().unwrap_or("<unknown>")
}
pub fn children(&self) -> impl Iterator<Item = Node> {
self.node.children().map(from_dt_node)
}
fn is_parent_root(&self) -> bool {
let Some(parent) = self.node.parent() else {
return true;
};
parent.parent().is_none()
}
}
fn dt_match_compatible(compatible: &str) -> Option<DevTreeProbe> {
iter_dt_probes().find(|probe| probe.compatible.contains(&compatible))
unsafe impl Sync for Node {}
unsafe impl Send for Node {}
static DRIVERS: IrqSafeRwLock<Vec<Driver>> = IrqSafeRwLock::new(Vec::new());
static BUS_RANGES: OneTimeInit<
IrqSafeRwLock<DiscreteRangeMap<u64, InclusiveInterval<u64>, Arc<dyn Bus>>>,
> = OneTimeInit::new();
static BUSSES: IrqSafeRwLock<Vec<Arc<dyn Bus>>> = IrqSafeRwLock::new(Vec::new());
pub fn register_driver(driver: Driver) {
DRIVERS.write().push(driver);
}
/// "Probes" a device tree node for any matching device, registering it if a compatible driver is
/// found
pub fn probe_dt_node(dt: &DevTreeNodeInfo) -> Option<Arc<dyn Device>> {
// TODO use list, not just the first item
let compatible = dt.node.prop("compatible")?;
let probe = dt_match_compatible(compatible)?;
let device = (probe.probe_func)(dt)?;
Some(device)
pub fn register_bus<I: IntoIterator<Item = Range<u64>>>(bus: Arc<dyn Bus>, ranges: I) {
let bus_ranges = BUS_RANGES.or_init_with(|| IrqSafeRwLock::new(DiscreteRangeMap::new()));
let mut bus_ranges = bus_ranges.write();
for range in ranges {
let range = InclusiveInterval::from(range.start..range.end);
bus_ranges.insert_strict(range, bus.clone()).ok();
}
BUSSES.write().push(bus);
}
/// Performs shallow walk of a device tree node and executes the visitor function on each node
pub fn enumerate_dt<
'a,
I: Iterator<Item = DevTreeIndexNode<'a, 'a, 'a>>,
F: FnMut(&str, DevTreeNodeInfo) -> Result<(), Error>,
>(
address_cells: usize,
size_cells: usize,
nodes: I,
mut f: F,
) -> Result<(), usize> {
let mut failed_count = 0;
pub fn enumerate_busses(dt: &'static DeviceTree, handler: fn(Arc<dyn Device>)) {
// Enumerate root first
for node in dt.root().children() {
let node = from_dt_node(node);
for node in nodes {
// Skip /cpus and /memory*
let probe = DevTreeNodeInfo {
address_cells,
size_cells,
node,
};
let Ok(name) = probe.node.name() else {
continue;
};
let Some(compatible) = probe.node.prop("compatible") else {
continue;
};
if let Err(error) = f(compatible, probe) {
log::warn!("{}: {:?}", name, error);
failed_count += 1;
if let Some(device) = node.probe() {
handler(device);
}
}
if failed_count == 0 {
Ok(())
} else {
Err(failed_count)
let busses = BUSSES.read();
for bus in busses.iter() {
bus.enumerate(handler);
}
}
pub fn map_bus_range(bus_range: Range<u64>) -> Option<Range<u64>> {
let bus_ranges = BUS_RANGES.try_get()?;
let bus_ranges = bus_ranges.read();
let query = InclusiveInterval::from(bus_range.start..bus_range.end);
let mut iter = bus_ranges.overlapping(query);
let (_, bus) = iter.next()?;
if iter.next().is_some() {
// Cannot cross multiple bus segments with one range
return None;
}
bus.map_range(bus_range).ok()
}
fn from_dt_node(node: TNode<'static>) -> Node {
// TODO support "compatible" as a <stringlist>
let compatible = node.prop("compatible");
Node { compatible, node }
}
pub fn dt_path(dt: &'static DeviceTree, path: &str) -> Result<Node, Error> {
let path = dt.resolve_alias(path).ok_or(Error::DoesNotExist)?;
let node = dt.find_absolute(path).ok_or(Error::DoesNotExist)?;
Ok(from_dt_node(node))
}
// /// "Probes" a device tree node for any matching device, registering it if a compatible driver is
// /// found
// pub fn probe_dt_node(dt: &DevTreeNodeInfo) -> Option<Arc<dyn Device>> {
// // TODO use list, not just the first item
// let compatible = dt.node.prop("compatible")?;
// let probe = dt_match_compatible(compatible)?;
// let device = (probe.probe_func)(dt)?;
// Some(device)
// }
//
// /// Performs a walk of a device tree, visiting all nodes which are parts of the given path
// pub fn traverse_dt_path<'a, F: FnMut(&str, DevTreeNodeInfo) -> Result<(), Error>>(
// root: &TNode,
// mut f: F,
// ) -> Result<(), Error> {
// loop {}
// }
//
// /// Performs shallow walk of a device tree node and executes the visitor function on each node
// pub fn enumerate_dt<
// 'a,
// I: Iterator<Item = DevTreeIndexNode<'a, 'a, 'a>>,
// F: FnMut(&str, DevTreeNodeInfo) -> Result<(), Error>,
// >(
// address_cells: usize,
// size_cells: usize,
// nodes: I,
// mut f: F,
// ) -> Result<(), usize> {
// let mut failed_count = 0;
//
// for node in nodes {
// // Skip /cpus and /memory*
// let probe = DevTreeNodeInfo {
// address_cells,
// size_cells,
// node,
// };
//
// let Ok(name) = probe.node.name() else {
// continue;
// };
// let Some(compatible) = probe.node.prop("compatible") else {
// continue;
// };
//
// if let Err(error) = f(compatible, probe) {
// log::warn!("{}: {:?}", name, error);
// failed_count += 1;
// }
// }
//
// if failed_count == 0 {
// Ok(())
// } else {
// Err(failed_count)
// }
// }

View File

@ -4,6 +4,7 @@ use fdt_rs::{
base::DevTree,
index::{iters::DevTreeIndexNodeSiblingIter, DevTreeIndex, DevTreeIndexNode, DevTreeIndexProp},
prelude::PropReader,
spec::Phandle,
};
use libk_mm::{address::PhysicalAddress, phys::PhysicalMemoryRegion};
use yggdrasil_abi::error::Error;
@ -26,6 +27,58 @@ pub type TNode<'a> = DevTreeIndexNode<'a, 'a, 'a>;
/// Device tree property
pub type TProp<'a> = DevTreeIndexProp<'a, 'a, 'a>;
pub struct CellTupleIterator<'a, P: DevTreeIndexPropExt + ?Sized, T: CellTuple> {
property: &'a P,
offset: usize,
sizes: T::Sizes,
}
pub trait CellTuple: Sized {
type Sizes: Copy;
fn read<P: DevTreeIndexPropExt + ?Sized>(
property: &P,
offset: usize,
sizes: Self::Sizes,
) -> Option<Self>;
fn entry_size(sizes: Self::Sizes) -> usize;
}
impl CellTuple for (u64, u64) {
type Sizes = (usize, usize);
fn read<P: DevTreeIndexPropExt + ?Sized>(
property: &P,
offset: usize,
sizes: Self::Sizes,
) -> Option<Self> {
let v0 = property.read_cell(offset, sizes.0)?;
let v1 = property.read_cell(offset + sizes.0, sizes.1)?;
Some((v0, v1))
}
fn entry_size(sizes: Self::Sizes) -> usize {
sizes.0 + sizes.1
}
}
impl CellTuple for (u64, u64, u64) {
type Sizes = (usize, usize, usize);
fn read<P: DevTreeIndexPropExt + ?Sized>(
property: &P,
offset: usize,
sizes: Self::Sizes,
) -> Option<Self> {
let v0 = property.read_cell(offset, sizes.0)?;
let v1 = property.read_cell(offset + sizes.0, sizes.1)?;
let v2 = property.read_cell(offset + sizes.0 + sizes.1, sizes.2)?;
Some((v0, v1, v2))
}
fn entry_size(sizes: Self::Sizes) -> usize {
sizes.0 + sizes.1 + sizes.2
}
}
/// Helper trait to provide extra functionality for [DevTreeIndexProp]
#[allow(clippy::len_without_is_empty)]
pub trait DevTreeIndexPropExt {
@ -37,27 +90,74 @@ pub trait DevTreeIndexPropExt {
/// Reads a cell value from the property at given offset
fn read_cell(&self, u32_offset: usize, cell_size: usize) -> Option<u64>;
fn read_cells<T: CellTuple>(&self, index: usize, sizes: T::Sizes) -> Option<T> {
let offset = index * T::entry_size(sizes);
T::read(self, offset, sizes)
}
fn iter_cells<T: CellTuple>(&self, sizes: T::Sizes) -> CellTupleIterator<Self, T> {
CellTupleIterator {
property: self,
offset: 0,
sizes,
}
}
/// Returns the length in bytes
fn len(&self) -> usize;
}
impl<P: DevTreeIndexPropExt, T: CellTuple> Iterator for CellTupleIterator<'_, P, T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let size = T::entry_size(self.sizes);
let entry = T::read(self.property, self.offset, self.sizes)?;
self.offset += size;
Some(entry)
}
}
/// Helper trait to provide extra functionality for [DevTreeIndexNode]
pub trait DevTreeIndexNodeExt {
/// Returns the root node's `#address-cells` property, or the default value defined by the
/// specification if it's absent
fn address_cells(&self) -> usize {
self.get_address_cells().unwrap_or(2)
match self.find_parent_property("#address-cells") {
Some(value) => value.u32(0).unwrap() as usize,
None => loop {
// TODO
core::hint::spin_loop();
},
}
}
/// Returns the root node's `#size-cells` property, or the default value defined by the
/// specification if it's absent
fn size_cells(&self) -> usize {
self.get_size_cells().unwrap_or(1)
match self.find_parent_property("#size-cells") {
Some(value) => value.u32(0).unwrap() as usize,
None => loop {
// TODO
core::hint::spin_loop();
},
}
}
/// Returns the #address-cells property of the node, if there is one
fn get_address_cells(&self) -> Option<usize>;
/// Returns the #size-cells property of the node, if there is one
fn get_size_cells(&self) -> Option<usize>;
fn find_parent_property(&self, name: &str) -> Option<DevTreeIndexProp>;
fn interrupt_parent(&self) -> Option<TNode>;
}
impl DevTreeIndexNodeExt for DevTreeIndexNode<'_, '_, '_> {
fn find_parent_property(&self, name: &str) -> Option<DevTreeIndexProp> {
let mut current = self.parent();
while let Some(node) = current {
if let Some(prop) = find_prop(&node, name) {
return Some(prop);
}
current = node.parent();
}
None
}
fn interrupt_parent(&self) -> Option<TNode> {
find_interrupt_controller(self)
}
}
/// Extension trait for [DevTreeIndexNode] to obtain typed property values
@ -70,8 +170,6 @@ pub trait DevTreeIndexNodePropGet<T> {
#[derive(Clone)]
pub struct FdtMemoryRegionIter<'a> {
inner: DevTreeIndexNodeSiblingIter<'a, 'a, 'a>,
address_cells: usize,
size_cells: usize,
}
/// Device tree wrapper struct
@ -80,36 +178,30 @@ pub struct DeviceTree<'a> {
index: DevTreeIndex<'a, 'a>,
}
/// Provides information about a device tree node to device driver's "probe" function
pub struct DevTreeNodeInfo<'a, 'i, 'dt> {
/// #address-cells property of the parent bus/system
pub address_cells: usize,
/// #size-cells property of the parent bus/system
pub size_cells: usize,
/// Device tree node being probed
pub node: DevTreeIndexNode<'a, 'i, 'dt>,
}
impl DeviceTree<'_> {
impl<'a> DeviceTree<'a> {
pub const MIN_HEADER_SIZE: usize = DevTree::MIN_HEADER_SIZE;
/// Constructs a device tree wrapper from the DTB virtual address.
///
/// # Safety
///
/// The caller must ensure the validity of the address.
/// The caller must ensure the validity of the address. This function is not safe
/// for use with multiple device trees.
#[allow(static_mut_refs)]
pub unsafe fn from_addr(virt: usize) -> Self {
#[allow(static_mut_refs)]
FDT_INDEX_BUFFER.0.fill(0);
let tree = DevTree::from_raw_pointer(virt as _).unwrap();
#[allow(static_mut_refs)]
let index = DevTreeIndex::new(tree, &mut FDT_INDEX_BUFFER.0).unwrap();
Self { tree, index }
Self::from_addr_with_index(virt, &mut FDT_INDEX_BUFFER.0)
}
/// Looks up a node for a given path
pub fn node_by_path(&self, path: &str) -> Option<TNode> {
find_node(self.index.root(), path.trim_start_matches('/'))
/// Constructs a device tree wrapper from the DTB virtual address and provided buffer
///
/// # Safety
///
/// The caller must ensure the validity of the address.
pub unsafe fn from_addr_with_index(virt: usize, index: &'static mut [u8]) -> Self {
let tree = DevTree::from_raw_pointer(core::ptr::with_exposed_provenance(virt)).unwrap();
let index = DevTreeIndex::new(tree, index).unwrap();
Self { tree, index }
}
/// Returns the total size of the device tree in memory
@ -117,29 +209,90 @@ impl DeviceTree<'_> {
self.tree.totalsize()
}
/// Returns the root node's `#address-cells` property, or the default value defined by the
/// specification if it's absent
pub fn address_cells(&self) -> usize {
self.index.root().address_cells()
}
/// Returns the root node's `#size-cells` property, or the default value defined by the
/// specification if it's absent
pub fn size_cells(&self) -> usize {
self.index.root().size_cells()
}
/// Returns the root node of the device tree
pub fn root(&self) -> DevTreeIndexNode {
self.index.root()
}
pub fn find_absolute(&'a self, path: &str) -> Option<TNode<'a>> {
let mut path = path.trim_start_matches('/');
let mut current = self.root();
'l0: loop {
if path.is_empty() {
return Some(current);
}
let (head, tail) = match path.split_once('/') {
Some(elem) => elem,
None => (path, ""),
};
path = tail;
for child in current.children() {
let Ok(name) = child.name() else {
continue;
};
if name == head {
current = child;
continue 'l0;
}
}
// Not found
return None;
}
}
pub fn find(&'a self, name: &str) -> Option<TNode<'a>> {
let path = self.resolve_alias(name)?;
self.find_absolute(path)
}
pub fn resolve_alias(&'a self, name: &'a str) -> Option<&'a str> {
if name.starts_with('/') {
Some(name)
} else {
// TODO cache aliases
// TODO cache phandles
let aliases = self.find_absolute("/aliases")?;
aliases.prop(name)
}
}
// Commonly used functions for convenience
/// Returns the /chosen.stdout-path value
pub fn chosen_stdout_path(&self) -> Option<&str> {
let chosen = self.node_by_path("/chosen")?;
chosen.prop("stdout-path")
/// Returns the /chosen.stdout-path value.
/// The value is in the following form:
///
/// ```ignore
/// stdout-path = "<device-name>[:<settings>]";
/// ```
pub fn chosen_stdout(&self) -> Option<(&str, Option<&str>)> {
let chosen = self.find_absolute("/chosen")?;
let value: &str = chosen.prop("stdout-path")?;
match value.split_once(':') {
Some((left, right)) => Some((left, Some(right))),
None => Some((value, None)),
}
}
pub fn chosen_initrd(&self) -> Option<(PhysicalAddress, PhysicalAddress)> {
let chosen = self.find_absolute("/chosen")?;
let address_cells = chosen.address_cells();
let initrd_start = find_prop(&chosen, "linux,initrd-start")?;
let initrd_end = find_prop(&chosen, "linux,initrd-end")?;
let initrd_start = initrd_start.cell1_array_item(0, address_cells)?;
let initrd_end = initrd_end.cell1_array_item(0, address_cells)?;
Some((
PhysicalAddress::from_u64(initrd_start),
PhysicalAddress::from_u64(initrd_end),
))
}
/// Returns the length of the header provided as a slice of bytes.
@ -155,20 +308,6 @@ impl DeviceTree<'_> {
}
}
impl DevTreeIndexNodeExt for DevTreeIndexNode<'_, '_, '_> {
fn get_address_cells(&self) -> Option<usize> {
self.props()
.find(|p| p.name().unwrap_or("") == "#address-cells")
.map(|p| p.u32(0).unwrap() as usize)
}
fn get_size_cells(&self) -> Option<usize> {
self.props()
.find(|p| p.name().unwrap_or("") == "#size-cells")
.map(|p| p.u32(0).unwrap() as usize)
}
}
impl DevTreeIndexPropExt for DevTreeIndexProp<'_, '_, '_> {
fn read_cell(&self, u32_offset: usize, cell_size: usize) -> Option<u64> {
match cell_size {
@ -203,13 +342,7 @@ impl<'a> FdtMemoryRegionIter<'a> {
/// Constructs a memory region iterator for given device tree
pub fn new(dt: &'a DeviceTree) -> Self {
let inner = dt.index.root().children();
let address_cells = dt.address_cells();
let size_cells = dt.size_cells();
Self {
inner,
address_cells,
size_cells,
}
Self { inner }
}
}
@ -218,26 +351,24 @@ impl Iterator for FdtMemoryRegionIter<'_> {
fn next(&mut self) -> Option<Self::Item> {
loop {
let Some(item) = self.inner.next() else {
let Some(node) = self.inner.next() else {
break None;
};
let name = item.name().unwrap_or("");
let name = node.name().unwrap_or("");
if name.starts_with("memory@") || name == "memory" {
let reg = item
.props()
.find(|p| p.name().unwrap_or("") == "reg")
.unwrap();
if (name.starts_with("memory@") || name == "memory")
&& let Some(reg) = find_prop(&node, "reg")
{
let address_cells = node.address_cells();
let size_cells = node.size_cells();
let (base, size) = reg
.cell2_array_item(0, self.address_cells, self.size_cells)
.unwrap();
if let Some((base, size)) = reg.cell2_array_item(0, address_cells, size_cells) {
let base = PhysicalAddress::from_u64(base);
let size = size.try_into().unwrap();
let base = PhysicalAddress::from_u64(base);
let size = size as usize;
break Some(PhysicalMemoryRegion { base, size });
break Some(PhysicalMemoryRegion { base, size });
}
}
}
}
@ -267,30 +398,150 @@ impl<'a> DevTreeIndexNodePropGet<&'a str> for DevTreeIndexNode<'a, '_, '_> {
}
}
pub fn dump_node<F: Fn(&str)>(node: &TNode, print: &F, indent: usize) {
fn tab<F: Fn(&str)>(print: &F, indent: usize) {
for _ in 0..indent {
print(" ");
}
}
let node_name = node.name().unwrap_or("<unknown>");
tab(print, indent);
print(node_name);
print(" {\r\n");
for prop in node.props() {
let name = prop.name().unwrap_or("<unknown>");
tab(print, indent + 1);
print(name);
print(": ");
match name {
_ if node_name == "aliases" => {
let model = prop.str().unwrap_or("<invalid>");
print("\"");
print(model);
print("\"");
}
"model" => {
let model = prop.str().unwrap_or("<invalid>");
print("\"");
print(model);
print("\"");
}
_ => {
print("???");
}
}
print("\r\n");
}
for child in node.children() {
dump_node(&child, print, indent + 1);
}
tab(print, indent);
print("}\r\n");
}
/// Looks up a property with given name in the node
pub fn find_prop<'a>(node: &TNode<'a>, name: &str) -> Option<TProp<'a>> {
node.props().find(|p| p.name().unwrap_or("") == name)
}
fn path_component_left(path: &str) -> (&str, &str) {
if let Some((left, right)) = path.split_once('/') {
(left, right.trim_start_matches('/'))
fn find_interrupt_controller<'a>(of: &TNode<'a>) -> Option<TNode<'a>> {
// Step 1. Find interrupt-parent property in the nearest ancestor
let interrupt_parent: Option<Phandle> = of.prop("interrupt-parent");
if let Some(phandle) = interrupt_parent {
of.index().nodes().find(|node| {
let value: Option<u32> = node.prop("phandle");
value.map(|value| value == phandle).unwrap_or(false)
})
} else if let Some(parent) = of.parent() {
find_interrupt_controller(&parent)
} else {
(path, "")
// No parents, no interrupt-controller property found
None
}
}
fn find_node<'a>(at: TNode<'a>, path: &str) -> Option<TNode<'a>> {
let (item, path) = path_component_left(path);
if item.is_empty() {
assert_eq!(path, "");
Some(at)
} else {
let child = at.children().find(|c| c.name().unwrap() == item)?;
if path.is_empty() {
Some(child)
} else {
find_node(child, path)
}
#[cfg(test)]
mod tests {
use alloc::{boxed::Box, vec::Vec};
use crate::dt::DevTreeIndexNodeExt;
use super::DeviceTree;
#[repr(C, align(0x1000))]
struct Buffer<A, D: ?Sized> {
_align: [A; 0],
data: D,
}
#[repr(C, align(0x1000))]
struct PageAlign;
static TEST1_DTB: &'static Buffer<PageAlign, [u8]> = &Buffer {
_align: [],
data: *include_bytes!("../dtbs/test1.dtb"),
};
fn test1_dtb() -> DeviceTree<'static> {
let buffer = &mut Box::leak(Box::new(Buffer::<PageAlign, _> {
_align: [],
data: [0; 65536],
}))
.data;
unsafe { DeviceTree::from_addr_with_index(TEST1_DTB.data.as_ptr().addr(), buffer) }
}
#[test]
fn test_absolute_path_lookup() {
let dtb = test1_dtb();
// Non-aliased lookups
// Root
let root = dtb.node_by_path("/").unwrap();
assert!(root.parent().is_none());
assert_eq!(root.name().unwrap(), "");
// Nested lookup
let soc = dtb.node_by_path("/soc").unwrap();
let uart = dtb.node_by_path("/soc/serial@10000000").unwrap();
assert!(soc.is_parent_of(&uart));
assert!(root.is_parent_of(&soc));
assert_eq!(soc.name().unwrap(), "soc");
}
#[test]
fn test_aliased_lookup() {
let dtb = test1_dtb();
let serial = dtb.node_by_path("serial").unwrap();
let uart = dtb.node_by_path("/soc/serial@10000000").unwrap();
assert!(serial == uart);
assert_eq!(serial.name().unwrap(), "serial@10000000");
}
#[test]
fn test_chosen_stdout() {
let dtb = test1_dtb();
let (stdout, settings) = dtb.chosen_stdout().unwrap();
assert_eq!(stdout, "serial");
assert_eq!(settings, Some("115200n8"));
}
#[test]
fn test_find_interrupt_controller() {
let dtb = test1_dtb();
let serial = dtb.node_by_path("serial").unwrap();
let serial_interrupt_parent = serial.interrupt_parent().unwrap();
let intc = dtb
.node_by_path("/soc/interrupt-controller@10001000")
.unwrap();
assert!(intc == serial_interrupt_parent);
}
}

View File

@ -1,5 +1,6 @@
#![no_std]
#![allow(clippy::missing_transmute_annotations)]
#![cfg_attr(any(not(test), rust_analyzer), no_std)]
#![feature(trait_alias, let_chains, decl_macro)]
#![allow(clippy::missing_transmute_annotations, clippy::type_complexity)]
extern crate alloc;

View File

@ -1,6 +1,6 @@
use core::ops::Range;
use kernel_arch::mem::PhysicalMemoryAllocator;
use kernel_arch::{mem::PhysicalMemoryAllocator, Architecture, ArchitectureImpl};
use libk_mm_interface::address::PhysicalAddress;
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
use yggdrasil_abi::{error::Error, system::SystemMemoryStats};
@ -32,6 +32,7 @@ pub struct PhysicalMemoryRegion {
// 8 * 4096 bits per page, 1 page per bit
const MEMORY_UPPER_LIMIT: PhysicalAddress =
PhysicalAddress::from_u64(TRACKED_PAGE_LIMIT as u64 * 4096);
const MEMORY_LOWER_LIMIT: PhysicalAddress = PhysicalAddress::from_usize(L3_PAGE_SIZE);
/// Global physical memory manager
pub static PHYSICAL_MEMORY: OneTimeInit<IrqSafeSpinlock<PhysicalMemoryManager>> =
@ -49,9 +50,13 @@ impl PhysicalMemoryRegion {
}
/// Constrains the [PhysicalMemoryRegion] to global memory limits set in the kernel
pub fn clamp(self, limit: PhysicalAddress) -> Option<(PhysicalAddress, PhysicalAddress)> {
let start = self.base.min(limit);
let end = self.end().min(limit);
pub fn clamp(
self,
lower_limit: PhysicalAddress,
upper_limit: PhysicalAddress,
) -> Option<(PhysicalAddress, PhysicalAddress)> {
let start = self.base.clamp(lower_limit, upper_limit);
let end = self.end().clamp(lower_limit, upper_limit);
if start < end {
Some((start, end))
@ -115,7 +120,10 @@ fn physical_memory_range<I: Iterator<Item = PhysicalMemoryRegion>>(
let mut start = PhysicalAddress::MAX;
let mut end = PhysicalAddress::MIN;
for (reg_start, reg_end) in it.into_iter().filter_map(|r| r.clamp(MEMORY_UPPER_LIMIT)) {
for (reg_start, reg_end) in it
.into_iter()
.filter_map(|r| r.clamp(PhysicalAddress::ZERO, MEMORY_UPPER_LIMIT))
{
if reg_start < start {
start = reg_start;
}
@ -136,7 +144,10 @@ pub fn find_contiguous_region<I: Iterator<Item = PhysicalMemoryRegion>>(
it: I,
count: usize,
) -> Option<PhysicalAddress> {
for (reg_start, reg_end) in it.into_iter().filter_map(|r| r.clamp(MEMORY_UPPER_LIMIT)) {
for (reg_start, reg_end) in it
.into_iter()
.filter_map(|r| r.clamp(MEMORY_LOWER_LIMIT, MEMORY_UPPER_LIMIT))
{
let mut collected = 0;
let mut base_addr = None;
@ -196,7 +207,7 @@ pub unsafe fn init_from_iter<
);
if phys_start.into_u64() & (L2_PAGE_SIZE as u64 - 1) != 0 {
todo!();
ArchitectureImpl::halt();
}
let mut manager = PhysicalMemoryManager::new(
@ -206,10 +217,14 @@ pub unsafe fn init_from_iter<
.expect("Memory start didn't fit in usize"),
total_count,
);
let mut collected = 0;
const MAX_MEMORY: usize = 64 * 1024;
for (start, end) in it.into_iter().filter_map(|r| r.clamp(MEMORY_UPPER_LIMIT)) {
for (start, end) in it
.into_iter()
.filter_map(|r| r.clamp(MEMORY_LOWER_LIMIT, MEMORY_UPPER_LIMIT))
{
for page in (start..end).step_by(L3_PAGE_SIZE) {
if collected >= MAX_MEMORY {
break;

View File

@ -335,7 +335,7 @@ impl<TA: TableAllocator> ProcessAddressSpace<TA> {
ProcessAddressSpaceImpl::<TA>::LOWER_LIMIT_PFN,
ProcessAddressSpaceImpl::<TA>::UPPER_LIMIT_PFN,
);
log::info!("New AddressSpace {:#x}", table.as_address_with_asid());
log::debug!("New AddressSpace {:#x}", table.as_address_with_asid());
Ok(Self {
inner: IrqSafeSpinlock::new(Inner { table, allocator }),
})
@ -465,7 +465,7 @@ impl<TA: TableAllocator> ProcessAddressSpace<TA> {
impl<TA: TableAllocator> Drop for ProcessAddressSpace<TA> {
fn drop(&mut self) {
log::info!("Drop AddressSpace {:#x}", self.as_address_with_asid());
log::debug!("Drop AddressSpace {:#x}", self.as_address_with_asid());
self.clear().ok();
}
}

View File

@ -532,7 +532,7 @@ impl Process {
self.cleanup(inner);
self.exit.signal(code);
if let Some(parent) = self.parent.as_ref().and_then(Weak::upgrade) {
log::info!("{}: notify parent ({}) of exit", self.id, parent.id);
log::debug!("{}: notify parent ({}) of exit", self.id, parent.id);
// Notify parent of child exit
parent.child_exit_notify.signal_saturating();
}
@ -564,7 +564,7 @@ impl Process {
continue;
}
log::info!("Terminate thread {}", thread.id);
log::debug!("Terminate thread {}", thread.id);
thread.terminate();
thread.exit.wait().await;
log::debug!("{} died", thread.id);
@ -576,7 +576,7 @@ impl Process {
impl Drop for Process {
fn drop(&mut self) {
log::info!("Drop Process {}", self.id);
log::debug!("Drop Process {}", self.id);
}
}

View File

@ -492,7 +492,7 @@ impl GlobalThreadList {
impl Drop for Thread {
fn drop(&mut self) {
log::info!("Drop Thread {}", self.id);
log::debug!("Drop Thread {}", self.id);
}
}

View File

@ -185,15 +185,31 @@ impl Node {
Self::new(data, flags, Metadata::default_dir())
}
/// Creates a new file node with given [RegularImpl]
pub fn regular<T: RegularImpl + 'static>(data: T, flags: NodeFlags) -> NodeRef {
/// Creates a new file node with given [RegularImpl] and permissions
pub fn regular_kernel<T: RegularImpl + 'static>(
data: T,
flags: NodeFlags,
mode: FileMode,
) -> NodeRef {
Self::new(
NodeImpl::Regular(Box::new(data)),
flags,
Metadata::default_file(),
Metadata {
mode,
uid: UserId::root(),
gid: GroupId::root(),
block_size: 0,
block_count: 0,
inode: None,
},
)
}
/// Creates a new file node with given [RegularImpl]
pub fn regular<T: RegularImpl + 'static>(data: T, flags: NodeFlags) -> NodeRef {
Self::regular_kernel(data, flags, FileMode::default_file())
}
/// Creates a new block device node with given [BlockDevice]
pub fn block(device: Arc<dyn BlockDevice>, flags: NodeFlags) -> NodeRef {
Self::new(

View File

@ -213,6 +213,7 @@ impl Node {
return Ok(dir.children.lock().len() as _);
}
log::warn!("Possible bug: node has IN_MEMORY_SIZE, but is not a directory");
Err(Error::NotImplemented)
} else {
// Fetch the size from the node

View File

@ -15,7 +15,7 @@ use yggdrasil_abi::{
error::Error,
io::{
DeviceRequest, KeyboardKey, KeyboardKeyEvent, TerminalInputOptions, TerminalLineOptions,
TerminalOptions, TerminalSize,
TerminalOptions, TerminalOutputOptions, TerminalSize,
},
process::{ProcessGroupId, Signal},
};
@ -143,6 +143,9 @@ impl<O: TerminalOutput> Terminal<O> {
if byte == b'\n' {
// TODO NL_TO_CRNL
if config.is_echo_newline() {
if config.output.contains(TerminalOutputOptions::NL_TO_CRNL) {
self.output.write(b'\r').ok();
}
self.output.write(byte).ok();
}
} else if byte.is_ascii_control() {

View File

@ -19,29 +19,6 @@
movk \reg, #:abs_g0_nc:\sym
.endm
.macro LEAVE_EL2, ret_label
mrs x8, CNTHCTL_EL2
orr x8, x8, #(CNTHCTL_EL2_EL1PCTEN | CNTHCTL_EL2_EL1PCEN)
msr CNTHCTL_EL2, x8
msr CNTVOFF_EL2, xzr
MOV_L x8, SCTLR_EL2_RES1
msr SCTLR_EL2, x8
mov x8, #HCR_EL2_RW_EL1IsAArch64
msr HCR_EL2, x8
mov x8, #SPSR_EL2_EL1h
orr x8, x8, #SPSR_EL2_MASK_DAIF
msr SPSR_EL2, x8
adr x8, \ret_label
msr ELR_EL2, x8
isb
eret
.endm
.global __aarch64_entry
.global __aarch64_ap_entry
@ -49,64 +26,107 @@
__aarch64_entry:
// x0 -- dtb_phys
// Multiple processor cores may or may not be running at this point
//////////////////////////////
// Check CPU index //
//////////////////////////////
mrs x1, mpidr_el1
ands x1, x1, #0xF
bne 1f
ands x1, x1, #3
bne .spin_wait
mrs x8, CurrentEL
lsr x8, x8, #2
cmp x8, #2
bne .el1
// Leave EL2
.el2:
LEAVE_EL2 .el1
.el1:
dsb sy
isb
// Zero .bss
MOV_ABS x8, __bss_start_phys
MOV_ABS x9, __bss_end_phys
// Zero .bss
1:
cmp x8, x9
beq 2f
strb wzr, [x8]
add x8, x8, #1
b 1b
2:
// BSP in SMP or uniprocessor
ldr x1, ={stack_bottom} + {stack_size} - {kernel_virt_offset}
//////////////////////////////
// Setup stack //
//////////////////////////////
MOV_ABS x1, {stack_bottom} + {stack_size} - {kernel_virt_offset}
mov sp, x1
bl {kernel_lower_entry} - {kernel_virt_offset}
//////////////////////////////
// Check CurrentEL //
//////////////////////////////
mrs x1, CurrentEL
lsr x1, x1, #2
and x1, x1, #2
MOV_ABS x2, .el_table
ldr x2, [x2, x1, lsl #3]
br x2
// TODO code for leaving EL3
.el3:
b .
.el2:
//////////////////////////////
// Leave EL2 //
//////////////////////////////
// Setup stack for EL1
MOV_ABS x1, {stack_bottom} + {stack_size} - {kernel_virt_offset}
msr sp_el1, x1
// Setup EL1 physical timer
mrs x1, cnthctl_el2
orr x1, x1, #(1 << 0) // EL0PCTEN = 1
orr x1, x1, #(1 << 1) // EL0VCTEN = 1
orr x1, x1, #(1 << 10) // EL1PCTEN = 1
orr x1, x1, #(1 << 11) // EL1PTEN = 1
msr cnthctl_el2, x1
mov x1, #1
msr cnthp_ctl_el2, x1
msr cntkctl_el1, xzr
msr cntvoff_el2, xzr
mov x1, #(1 << 31) // RW = 1, EL1 is AArch64
orr x1, x1, #(1 << 1) // SWIO = 1 (Pi has this hardwired)
msr hcr_el2, x1
// Return to EL1
mov x1, #0x3C5
msr spsr_el2, x1
adr x1, .el1
msr elr_el2, x1
eret
.el1:
//////////////////////////////
// Setup EL1 for Rust entry //
//////////////////////////////
// Load proper SP
MOV_ABS x1, {stack_bottom} + {stack_size} - {kernel_virt_offset}
mov sp, x1
// Zero .bss
MOV_ABS x1, __bss_start_phys
MOV_ABS x2, __bss_size
lsr x2, x2, #3
cbz x2, 11f
10:
str xzr, [x1]
add x1, x1, #8
subs x2, x2, #1
bne 10b
11:
// Perform Rust code entry
bl {bsp_el1_entry} - {kernel_virt_offset}
b .
// EL0 impossible
.el0:
udf #0
.spin_wait:
// AP in a SMP system
// TODO spin loop for this method of init
1:
b .
wfe
b .spin_wait
.p2align 3
.el_table:
.dword .el0
.dword .el1
.dword .el2
.dword .el3
.section .text
__aarch64_ap_entry:
// x0 -- physical sp
mrs x8, CurrentEL
lsr x8, x8, #2
cmp x8, #2
bne .ap_el1
.ap_el2:
LEAVE_EL2 .ap_el1
.ap_el1:
dsb sy
isb
mov sp, x0
MOV_ABS x0, {kernel_ap_lower_entry} - {kernel_virt_offset}
blr x0
b .

View File

@ -1,63 +1,82 @@
//! AArch64 boot and entry implementation
use core::{arch::global_asm, sync::atomic::Ordering};
use core::arch::global_asm;
use aarch64_cpu::{
asm::barrier,
registers::{CPACR_EL1, ID_AA64MMFR0_EL1, MAIR_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1},
};
use kernel_arch::{absolute_address, Architecture, ArchitectureImpl};
use kernel_arch_aarch64::CPU_COUNT;
use kernel_arch_aarch64::mem::ic_iallu;
use libk::{devfs, task::runtime};
use libk_mm::{
address::{PhysicalAddress, Virtualize},
phys,
table::EntryLevel,
};
use libk_mm::address::PhysicalAddress;
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
use super::{exception, BootStack, PLATFORM};
use crate::{
arch::{aarch64::BOOT_STACK_SIZE, L3},
kernel_main, kernel_secondary_main,
mem::KERNEL_VIRT_OFFSET,
};
use crate::{arch::aarch64::BOOT_STACK_SIZE, kernel_main, mem::KERNEL_VIRT_OFFSET};
unsafe fn pre_init_mmu() {
unsafe fn check_mmu_features() {
if ID_AA64MMFR0_EL1.matches_all(ID_AA64MMFR0_EL1::TGran4::NotSupported) {
// TODO early panic
ArchitectureImpl::halt();
}
}
unsafe fn pre_init_mmu() {
// TODO: Figure out why WriteBack_NonTransient_ReadWriteAlloc doesn't work on Pi 4B
MAIR_EL1.write(
//// Attribute 0 -- normal memory
// Inner
MAIR_EL1::Attr0_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc +
MAIR_EL1::Attr0_Normal_Inner::WriteBack_NonTransient +
// Outer
MAIR_EL1::Attr0_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc +
MAIR_EL1::Attr0_Normal_Outer::WriteBack_NonTransient +
//// Attribute 1 -- device memory
MAIR_EL1::Attr1_Device::nonGathering_nonReordering_EarlyWriteAck,
);
TCR_EL1.modify(
TCR_EL1.write(
TCR_EL1::AS::ASID8Bits +
TCR_EL1::A1::TTBR0 +
// General
TCR_EL1::IPS::Bits_48 +
// TTBR0
TCR_EL1::TG0::KiB_4 + TCR_EL1::T0SZ.val(25) + TCR_EL1::SH0::Inner +
TCR_EL1::TG0::KiB_4 + TCR_EL1::T0SZ.val(25) + TCR_EL1::SH0::Outer +
// TTBR1
TCR_EL1::TG1::KiB_4 + TCR_EL1::T1SZ.val(25) + TCR_EL1::SH1::Outer,
);
}
unsafe fn enable_mmu() {
barrier::dmb(barrier::ISH);
barrier::dsb(barrier::ISHST);
barrier::isb(barrier::SY);
SCTLR_EL1.modify(
// Enable translation
SCTLR_EL1::M::Enable +
// NOTE if anything breaks due to caching, find out :)
SCTLR_EL1::I::NonCacheable + SCTLR_EL1::C::NonCacheable,
SCTLR_EL1::E0E::LittleEndian
+ SCTLR_EL1::EE::LittleEndian
+ SCTLR_EL1::WXN::Disable
+ SCTLR_EL1::SA0::Enable
+ SCTLR_EL1::SA::Enable
+ SCTLR_EL1::A::Enable
+ SCTLR_EL1::I::NonCacheable
+ SCTLR_EL1::C::NonCacheable,
);
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
// Enable translation
SCTLR_EL1.modify(SCTLR_EL1::M::Enable);
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
ic_iallu();
SCTLR_EL1.modify(SCTLR_EL1::I::Cacheable);
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
SCTLR_EL1.modify(SCTLR_EL1::C::Cacheable);
barrier::isb(barrier::SY);
}
@ -80,6 +99,7 @@ unsafe extern "C" fn __aarch64_el1_bsp_lower_entry(dtb: PhysicalAddress) -> ! {
CPACR_EL1.modify(CPACR_EL1::FPEN::TrapNothing);
// Setup MMU to jump to "higher-half" address space
check_mmu_features();
pre_init_mmu();
kernel_arch_aarch64::mem::load_fixed_tables();
enable_mmu();
@ -95,10 +115,15 @@ unsafe extern "C" fn __aarch64_bsp_upper_entry(dtb: PhysicalAddress) -> ! {
// Remove the "lower-half" mapping, no longer needed
TTBR0_EL1.set(0);
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
// Setup the "runtime" part of the kernel tables
PLATFORM
.init_memory_management(dtb)
.expect("Could not initialize memory management");
if PLATFORM.init_memory_management(dtb).is_err() {
ArchitectureImpl::halt();
}
barrier::dsb(barrier::ISHST);
barrier::isb(barrier::SY);
exception::init_exceptions();
@ -108,51 +133,54 @@ unsafe extern "C" fn __aarch64_bsp_upper_entry(dtb: PhysicalAddress) -> ! {
runtime::init_task_queue();
// Initialize the BSP CPU + the devices
PLATFORM
.init_platform(true)
.expect("Could not initialize the platform");
if PLATFORM.init_platform(true).is_err() {
ArchitectureImpl::halt();
}
kernel_main()
}
// TODO re-implement for Pi 4B
unsafe extern "C" fn __aarch64_el1_ap_lower_entry() -> ! {
const AP_STACK_PAGES: usize = 8;
ArchitectureImpl::set_interrupt_mask(true);
ArchitectureImpl::halt();
// const AP_STACK_PAGES: usize = 8;
// ArchitectureImpl::set_interrupt_mask(true);
// Unmask FP operations
CPACR_EL1.modify(CPACR_EL1::FPEN::TrapNothing);
// // Unmask FP operations
// CPACR_EL1.modify(CPACR_EL1::FPEN::TrapNothing);
pre_init_mmu();
kernel_arch_aarch64::mem::load_fixed_tables();
enable_mmu();
// pre_init_mmu();
// kernel_arch_aarch64::mem::load_fixed_tables();
// enable_mmu();
let stack_pages = phys::alloc_pages_contiguous(AP_STACK_PAGES).unwrap();
let stack_base = stack_pages.virtualize();
let sp = stack_base + L3::SIZE * AP_STACK_PAGES;
// let stack_pages = phys::alloc_pages_contiguous(AP_STACK_PAGES).unwrap();
// let stack_base = stack_pages.virtualize();
// let sp = stack_base + L3::SIZE * AP_STACK_PAGES;
let elr = absolute_address!(__aarch64_ap_upper_entry);
// let elr = absolute_address!(__aarch64_ap_upper_entry);
enter_higher_half(sp, elr, 0);
// enter_higher_half(sp, elr, 0);
}
extern "C" fn __aarch64_ap_upper_entry() -> ! {
barrier::dmb(barrier::ISH);
barrier::isb(barrier::SY);
ArchitectureImpl::halt();
// barrier::dmb(barrier::ISH);
// barrier::isb(barrier::SY);
let cpu_id = CPU_COUNT.fetch_add(1, Ordering::SeqCst);
aarch64_cpu::asm::sev();
// let cpu_id = CPU_COUNT.fetch_add(1, Ordering::SeqCst);
// aarch64_cpu::asm::sev();
log::info!("cpu{} initializing", cpu_id);
// log::info!("cpu{} initializing", cpu_id);
exception::init_exceptions();
// exception::init_exceptions();
unsafe {
PLATFORM
.init_platform(false)
.expect("Could not initialize the AP");
}
// unsafe {
// PLATFORM
// .init_platform(false)
// .expect("Could not initialize the AP");
// }
kernel_secondary_main()
// kernel_secondary_main()
}
#[link_section = ".bss"]
@ -160,8 +188,7 @@ static BSP_STACK: BootStack<BOOT_STACK_SIZE> = BootStack::zeroed();
global_asm!(
include_str!("entry.S"),
kernel_lower_entry = sym __aarch64_el1_bsp_lower_entry,
kernel_ap_lower_entry = sym __aarch64_el1_ap_lower_entry,
bsp_el1_entry = sym __aarch64_el1_bsp_lower_entry,
stack_bottom = sym BSP_STACK,
kernel_virt_offset = const KERNEL_VIRT_OFFSET,
stack_size = const BOOT_STACK_SIZE

View File

@ -1,6 +1,9 @@
//! Exception and interrupt management functions
use core::arch::global_asm;
use core::{
arch::global_asm,
sync::atomic::{AtomicBool, Ordering},
};
use aarch64_cpu::{
asm::barrier,
@ -11,8 +14,12 @@ use aarch64_cpu::{
};
use abi::{process::Signal, SyscallFunction};
use kernel_arch::{sync::hack_locks, Architecture, ArchitectureImpl};
use kernel_arch_aarch64::context::ExceptionFrame;
use libk::{arch::Cpu, device::external_interrupt_controller, task::thread::Thread};
use kernel_arch_aarch64::{
context::ExceptionFrame,
mem::table::{EntryType, PageTable, L1, L2, L3},
};
use libk::{device::external_interrupt_controller, task::thread::Thread};
use libk_mm::{address::PhysicalAddress, table::EntryLevelExt};
use tock_registers::interfaces::{Readable, Writeable};
use crate::{
@ -31,8 +38,47 @@ pub fn init_exceptions() {
barrier::isb(barrier::SY);
}
unsafe fn perform_ptw<F: Fn(u32, EntryType)>(virt: usize, handler: F) {
let l1i = virt.page_index::<L1>();
let l2i = virt.page_index::<L2>();
let l3i = virt.page_index::<L3>();
let ttbr_phys = if virt > KERNEL_VIRT_OFFSET {
TTBR1_EL1.get()
} else {
TTBR0_EL1.get() & !1
};
// Strip ASID
let ttbr_phys = PhysicalAddress::from_u64(ttbr_phys & !(0xFFFF << 48));
let Some(l1) = PageTable::<L1>::from_physical(ttbr_phys) else {
handler(0, EntryType::Invalid);
return;
};
handler(0, EntryType::Table(ttbr_phys));
let l2 = l1.walk(l1i);
handler(1, l2);
let l2 = match l2 {
EntryType::Table(l2) => PageTable::<L2>::from_physical(l2).unwrap(),
_ => return,
};
let l3 = l2.walk(l2i);
handler(2, l3);
let l3 = match l3 {
EntryType::Table(l3) => PageTable::<L3>::from_physical(l3).unwrap(),
_ => return,
};
let l3e = match l3[l3i].as_page() {
Some(page) => EntryType::Page(page),
None => EntryType::Invalid,
};
handler(3, l3e);
}
fn dump_irrecoverable_exception(frame: &ExceptionFrame, ec: u64, iss: u64) {
let cpu = Cpu::try_local();
let far = FAR_EL1.get() as usize;
let ttbr0 = TTBR0_EL1.get();
@ -46,27 +92,39 @@ fn dump_irrecoverable_exception(frame: &ExceptionFrame, ec: u64, iss: u64) {
log::error!(target: "raw", "Register dump:\n");
log::error!(target: "raw", "{:?}\n", frame);
if let Some(cpu) = cpu {
// let current = cpu.queue().current_process();
let current = cpu.current_thread_id().and_then(Thread::get);
if let Some(current) = current {
log::error!(target: "raw", "In thread {}\n", current.id);
let space = current.address_space();
if far < KERNEL_VIRT_OFFSET && space.as_address_with_asid() == ttbr0 {
match space.translate(far) {
Ok(phys) => log::error!(
target: "raw",
"FAR translation: {:#x} -> {:#x}\n",
far,
phys
),
Err(_) => log::error!(target: "raw", "FAR does not translate\n"),
}
}
}
log::error!(target: "raw", "FAR_EL1 page table walk:\n");
unsafe {
perform_ptw(far, |level, result| {
log::error!(target: "raw", "[{level}] {result}\n");
});
}
// if let Some(cpu) = cpu {
// // let current = cpu.queue().current_process();
// let current = cpu.current_thread_id().and_then(Thread::get);
// if let Some(current) = current {
// log::error!(target: "raw", "In thread {}\n", current.id);
// let space = current.address_space();
// if far < KERNEL_VIRT_OFFSET && space.as_address_with_asid() == ttbr0 {
// match space.translate(far) {
// Ok(phys) => log::error!(
// target: "raw",
// "FAR translation (manual): {:#x} -> {:#x}\n",
// far,
// phys
// ),
// Err(_) => log::error!(target: "raw", "FAR does not translate\n"),
// }
// let at_s1e0r = at_s1e0r(far);
// let at_s1e1r = at_s1e1r(far);
// log::error!(target: "raw", "at s1e0r: {far:#x} -> {at_s1e0r:#x?}\n");
// log::error!(target: "raw", "at s1e1r: {far:#x} -> {at_s1e1r:#x?}\n");
// }
// }
// }
match ec {
// Data abort from lower level
@ -152,8 +210,15 @@ extern "C" fn __aa64_el0_serror_handler() {
}
// EL1
static EL1_FAULT_TAKEN: AtomicBool = AtomicBool::new(false);
#[no_mangle]
extern "C" fn __aa64_el1_sync_handler(frame: *mut ExceptionFrame) {
if EL1_FAULT_TAKEN.swap(true, Ordering::Release) {
// Fault already taken
ArchitectureImpl::halt();
}
let frame = unsafe { &*frame };
let esr_el1 = ESR_EL1.get();
let ec = (esr_el1 >> 26) & 0x3F;

View File

@ -76,9 +76,12 @@ impl GicdSharedRegs {
#[inline(always)]
fn itargets_slice(&self) -> &[ReadWrite<u32, ITARGETSR::Register>] {
assert!(self.num_irqs() >= 36);
let itargetsr_max_index = ((self.num_irqs() - 32) >> 2) - 1;
&self.ITARGETSR[0..itargetsr_max_index]
if self.num_irqs() >= 36 {
let itargetsr_max_index = ((self.num_irqs() - 32) >> 2) - 1;
&self.ITARGETSR[0..itargetsr_max_index]
} else {
&[]
}
}
}

View File

@ -13,7 +13,7 @@ use device_api::{
MessageInterruptController, MsiInfo,
},
};
use device_tree::{device_tree_driver, dt::DevTreeIndexPropExt};
use device_tree::driver::device_tree_driver;
use kernel_arch_aarch64::{GicInterface, CPU_COUNT};
use libk::{arch::Cpu, device::register_external_interrupt_controller, task::cpu_index};
use libk_mm::{
@ -52,6 +52,11 @@ impl Device for Gic {
}
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
log::debug!(
"Init GIC: gicd={:#x}, gicc={:#x}",
self.gicd_base,
self.gicc_base
);
let gicd_mmio = Arc::new(RawDeviceMemoryMapping::map(
self.gicd_base.into_u64(),
0x1000,
@ -113,6 +118,7 @@ impl ExternalInterruptController for Gic {
Irq::External(i) => i + 32,
Irq::Private(i) => i + 16,
} as usize;
log::info!("Enable irq{index} ({irq:?})");
gicd.enable_irq(index);
Ok(())
}
@ -219,15 +225,13 @@ impl GicPerCpu {
device_tree_driver! {
compatible: ["arm,cortex-a15-gic", "arm,gic-400"],
probe(dt) => {
let reg = device_tree::find_prop(&dt.node, "reg")?;
probe(of) => {
let gicd_range = of.mapped_range(0)?;
let gicc_range = of.mapped_range(1)?;
let (gicc_base, _) = reg.cell2_array_item(0, dt.address_cells, dt.size_cells)?;
let (gicd_base, _) = reg.cell2_array_item(1, dt.address_cells, dt.size_cells)?;
let gicd_base = PhysicalAddress::from_u64(gicd_range.start);
let gicc_base = PhysicalAddress::from_u64(gicc_range.start);
Some(Arc::new(unsafe { Gic::new(
PhysicalAddress::from_u64(gicc_base),
PhysicalAddress::from_u64(gicd_base),
)}))
Some(Arc::new(unsafe { Gic::new(gicd_base, gicc_base) }))
}
}

View File

@ -1,18 +1,22 @@
//! AArch64 architecture and platforms implementation
use core::sync::atomic::Ordering;
use core::sync::atomic::{self, Ordering};
use aarch64_cpu::registers::{CNTP_CTL_EL0, CNTP_TVAL_EL0};
use aarch64_cpu::{
asm::barrier,
registers::{CNTP_CTL_EL0, CNTP_TVAL_EL0, SCTLR_EL1},
};
use abi::error::Error;
use alloc::sync::Arc;
use device_api::{
interrupt::{Irq, LocalInterruptController},
ResetDevice,
};
use device_tree::dt::{DevTreeIndexPropExt, DevTreeNodeInfo, DeviceTree, FdtMemoryRegionIter};
use git_version::git_version;
use device_tree::dt::{DevTreeIndexNodePropGet, DeviceTree, FdtMemoryRegionIter};
use kernel_arch::Architecture;
use kernel_arch_aarch64::{
mem::{
ic_iallu,
table::{L1, L3},
EarlyMapping, MEMORY_LIMIT, RAM_MAPPING_L1_COUNT,
},
@ -25,18 +29,19 @@ use libk::{
};
use libk_mm::{
address::PhysicalAddress,
phys::PhysicalMemoryRegion,
phys::{self, reserved::reserve_region},
phys::{self, reserved::reserve_region, PhysicalMemoryRegion},
pointer::PhysicalRef,
table::EntryLevelExt,
};
use libk_util::OneTimeInit;
use tock_registers::interfaces::Writeable;
use tock_registers::interfaces::{ReadWriteable, Writeable};
use ygg_driver_pci::PciBusManager;
use crate::{
device::power::arm_psci::Psci,
// device::power::arm_psci::Psci,
device::MACHINE_NAME,
fs::{Initrd, INITRD_DATA},
util::call_init_array,
};
use self::gic::Gic;
@ -62,7 +67,7 @@ pub struct AArch64 {
dt: OneTimeInit<DeviceTree<'static>>,
/// Optional instance of PSCI on this platform
pub psci: OneTimeInit<Arc<Psci>>,
// pub psci: OneTimeInit<Arc<Psci>>,
reset: OneTimeInit<Arc<dyn ResetDevice>>,
initrd: OneTimeInit<PhysicalRef<'static, [u8]>>,
}
@ -95,12 +100,13 @@ impl Platform for AArch64 {
}
unsafe fn reset(&self) -> ! {
if let Some(reset) = self.reset.try_get() {
reset.reset()
} else {
let psci = self.psci.get();
psci.reset()
}
ArchitectureImpl::halt();
// if let Some(reset) = self.reset.try_get() {
// reset.reset()
// } else {
// let psci = self.psci.get();
// psci.reset()
// }
}
}
@ -111,25 +117,6 @@ impl AArch64 {
GIC.init(gic);
}
fn extract_initrd_from_dt(
&self,
dt: &DeviceTree,
) -> Option<(PhysicalAddress, PhysicalAddress)> {
let chosen = dt.node_by_path("/chosen")?;
let initrd_start = device_tree::find_prop(&chosen, "linux,initrd-start")?;
let initrd_end = device_tree::find_prop(&chosen, "linux,initrd-end")?;
let address_cells = dt.address_cells();
let initrd_start = initrd_start.cell1_array_item(0, address_cells)?;
let initrd_end = initrd_end.cell1_array_item(0, address_cells)?;
let initrd_start = PhysicalAddress::from_u64(initrd_start);
let initrd_end = PhysicalAddress::from_u64(initrd_end);
Some((initrd_start, initrd_end))
}
fn map_physical_memory<I: Iterator<Item = PhysicalMemoryRegion> + Clone>(
_it: I,
_memory_start: PhysicalAddress,
@ -159,7 +146,7 @@ impl AArch64 {
// Extract the size of the device tree
let dtb_size = {
let dtb_header = EarlyMapping::<u8>::map_slice(dtb, DeviceTree::MIN_HEADER_SIZE)?;
DeviceTree::read_totalsize(dtb_header.as_ref()).unwrap()
DeviceTree::read_totalsize(dtb_header.as_ref()).map_err(|_| Error::InvalidArgument)?
};
reserve_region(
@ -175,7 +162,7 @@ impl AArch64 {
let dt = DeviceTree::from_addr(dtb_slice.as_ptr() as usize);
// Setup initrd from the dt
let initrd = self.extract_initrd_from_dt(&dt);
let initrd = dt.chosen_initrd();
if let Some((start, end)) = initrd {
let aligned_start = start.page_align_down::<L3>();
@ -222,13 +209,58 @@ impl AArch64 {
Ok(())
}
unsafe fn init_platform(&self, is_bsp: bool) -> Result<(), Error> {
#[inline(never)]
unsafe fn setup_serial_output(&self, dt: &'static DeviceTree) -> Result<bool, Error> {
// Get /chosen.stdout-path to get early debug printing
// TODO honor defined configuration value
let stdout = dt.chosen_stdout();
let stdout_path = stdout.map(|(p, _)| p);
if let Some(path) = stdout_path {
let node = device_tree::driver::dt_path(dt, path)?;
let device = node.probe_recursive().ok_or(Error::DoesNotExist)?;
device.clone().init()?;
DEVICE_REGISTRY.add_pending_initialization(device, true);
Ok(true)
} else {
Ok(false)
}
}
fn machine_name(dt: &'static DeviceTree) -> (Option<&'static str>, Option<&'static str>) {
(dt.root().prop("compatible"), dt.root().prop("model"))
}
fn apply_machine_workarounds(compatible: &str) {
#[allow(clippy::single_match)]
match compatible {
"raspberrypi,4-model-b" => {
// XXX Workaround for Raspberry Pi 4B:
// Instruction cache totally freaks out at EL0 and I don't know why
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
ic_iallu();
SCTLR_EL1.modify(SCTLR_EL1::I::NonCacheable);
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
}
_ => (),
}
}
unsafe fn init_platform(&'static self, is_bsp: bool) -> Result<(), Error> {
let per_cpu = PerCpuData {
gic: OneTimeInit::new(),
};
Cpu::init_local(None, per_cpu);
if is_bsp {
call_init_array();
atomic::compiler_fence(Ordering::SeqCst);
ygg_driver_pci::register_vendor_driver(
"Virtio PCI GPU Device",
0x1AF4,
@ -256,59 +288,29 @@ impl AArch64 {
ygg_driver_usb_xhci::probe,
);
let dt = self.dt.get();
let address_cells = dt.address_cells();
let size_cells = dt.size_cells();
// Setup /chosen.stdout-path to get early debug printing
let chosen_stdout_path = dt.chosen_stdout_path();
let chosen_stdout = chosen_stdout_path.and_then(|path| dt.node_by_path(path));
if let Some(node) = chosen_stdout.clone() {
let probe = DevTreeNodeInfo {
address_cells,
size_cells,
node,
};
if let Some(device) = device_tree::driver::probe_dt_node(&probe) {
device.clone().init()?;
DEVICE_REGISTRY.add_pending_initialization(device, true);
}
}
debug::init();
log::info!(
"Yggdrasil v{} ({})",
env!("CARGO_PKG_VERSION"),
git_version!()
);
let dt = self.dt.get();
let (machine_compatible, machine_name) = Self::machine_name(dt);
if let Some(compatible) = machine_compatible {
Self::apply_machine_workarounds(compatible);
}
self.setup_serial_output(dt)?;
if let Some(machine) = machine_name {
log::info!("Running on {machine:?}");
MACHINE_NAME.init(machine.into());
}
log::info!("Initializing aarch64 platform");
let nodes = dt.root().children();
if let Err(error) =
device_tree::driver::enumerate_dt(address_cells, size_cells, nodes, |_, probe| {
// Skip chosen-stdout, already initialized
if let Some(ref chosen_stdout) = chosen_stdout
&& chosen_stdout.name() == probe.node.name()
{
return Ok(());
}
if let Some(device) = device_tree::driver::probe_dt_node(&probe) {
DEVICE_REGISTRY.add_pending_initialization(device, false);
}
Ok(())
})
{
log::warn!(
"{} errors encountered when initializing platform devices",
error
);
}
device_tree::driver::enumerate_busses(dt, |device| {
log::debug!("* {:?}", device.display_name());
DEVICE_REGISTRY.add_pending_initialization(device, false);
});
DEVICE_REGISTRY.run_initialization();
@ -345,6 +347,6 @@ pub static PLATFORM: AArch64 = AArch64 {
dt: OneTimeInit::new(),
initrd: OneTimeInit::new(),
psci: OneTimeInit::new(),
// psci: OneTimeInit::new(),
reset: OneTimeInit::new(),
};

View File

@ -2,11 +2,9 @@
use core::sync::atomic::Ordering;
use abi::error::Error;
use device_api::CpuBringupDevice;
use device_tree::dt::{DevTreeIndexNodePropGet, DeviceTree};
use kernel_arch_aarch64::CPU_COUNT;
use crate::arch::PLATFORM;
use crate::mem::KERNEL_VIRT_OFFSET;
use super::{BootStack, BOOT_STACK_SIZE};
@ -26,7 +24,7 @@ struct CpuInfo<'a> {
}
fn enumerate_cpus<'a>(dt: &'a DeviceTree) -> impl Iterator<Item = CpuInfo<'a>> {
let cpus = dt.node_by_path("/cpus").unwrap();
let cpus = dt.find_absolute("/cpus").unwrap();
cpus.children().filter_map(|cpu_node| {
let compatible = cpu_node.prop("compatible")?;
@ -53,21 +51,23 @@ fn enumerate_cpus<'a>(dt: &'a DeviceTree) -> impl Iterator<Item = CpuInfo<'a>> {
}
impl CpuEnableMethod {
unsafe fn start_cpu(&self, id: usize, ip: usize, sp: usize) -> Result<(), Error> {
log::info!("Start CPU #{id}");
match self {
Self::Psci => {
let psci = PLATFORM.psci.try_get().ok_or_else(|| {
log::warn!(
"cpu{} has to be enabled through PSCI, but no PSCI found",
id
);
Error::InvalidArgument
})?;
unsafe fn start_cpu(&self, _id: usize, _ip: usize, _sp: usize) -> Result<(), Error> {
log::warn!("PSCI temporarily disabled");
Err(Error::NotImplemented)
// log::info!("Start CPU #{id}");
// match self {
// Self::Psci => {
// let psci = PLATFORM.psci.try_get().ok_or_else(|| {
// log::warn!(
// "cpu{} has to be enabled through PSCI, but no PSCI found",
// id
// );
// Error::InvalidArgument
// })?;
psci.start_cpu(id, ip, sp)
}
}
// psci.start_cpu(id, ip, sp)
// }
// }
}
}

View File

@ -7,9 +7,9 @@ use abi::{error::Error, time::NANOSECONDS_IN_SECOND};
use alloc::sync::Arc;
use device_api::{
device::Device,
interrupt::{InterruptHandler, Irq},
interrupt::{InterruptHandler, Irq, IrqLevel, IrqOptions, IrqTrigger},
};
use device_tree::device_tree_driver;
use device_tree::driver::device_tree_driver;
use kernel_arch::task::Scheduler;
use libk::{arch::Cpu, device::external_interrupt_controller, task::runtime, time};
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
@ -33,11 +33,11 @@ impl InterruptHandler for ArmTimer {
if Cpu::local().id() == 0 {
let last = LAST_TICKS.swap(count, Ordering::Relaxed);
let freq = CNTFRQ_EL0.get();
let delta = count.wrapping_sub(last);
// Only update time from local CPU
let dt = delta * NANOSECONDS_IN_SECOND / freq;
time::add_nanoseconds(dt);
if let Some(delta) = count.checked_sub(last) {
// Only update time from local CPU
let dt = delta * NANOSECONDS_IN_SECOND / freq;
time::add_nanoseconds(dt);
}
}
runtime::tick();
@ -65,9 +65,18 @@ impl Device for ArmTimer {
}
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
log::info!("ARM Generic Timer frequency={}Hz", CNTFRQ_EL0.get());
let intc = external_interrupt_controller()?;
intc.register_irq(self.irq, Default::default(), self.clone())?;
intc.register_irq(
self.irq,
IrqOptions {
level: IrqLevel::ActiveLow,
trigger: IrqTrigger::Level,
},
self.clone(),
)?;
CNTP_CTL_EL0.modify(CNTP_CTL_EL0::IMASK::CLEAR);
CNTP_TVAL_EL0.set(TICK_INTERVAL);
@ -89,10 +98,15 @@ impl ArmTimer {
}
}
// TODO handle interrupt-parent
device_tree_driver! {
compatible: ["arm,armv8-timer"],
probe(_dt) => {
// TODO actually get info from the dt
Some(Arc::new(unsafe { ArmTimer::new(Irq::Private(14)) }))
// let interrupts = dt::find_prop(&dt.node, "interrupts")?;
// let irq_no = interrupts.read_cell(1, 1)? as u32;
// let irq = Irq::Private(irq_no);
let irq = Irq::Private(14);
Some(Arc::new(unsafe { ArmTimer::new(irq) }))
}
}

View File

@ -24,6 +24,8 @@ __aa\bits\()_el\el\ht\()_\kind:
.set PT_REGS_SIZE, (16 * 16 + 16 * 2)
.macro EXC_SAVE_STATE
isb sy
sub sp, sp, #PT_REGS_SIZE
stp x0, x1, [sp, #16 * 0]
@ -51,9 +53,14 @@ __aa\bits\()_el\el\ht\()_\kind:
stp x0, x1, [sp, #16 * 16]
stp x2, x3, [sp, #16 * 17]
dsb ish
isb sy
.endm
.macro EXC_RESTORE_STATE
dsb ishst
ldp x0, x1, [sp, #16 * 16]
ldp x2, x3, [sp, #16 * 17]
@ -81,6 +88,9 @@ __aa\bits\()_el\el\ht\()_\kind:
ldp x30, x31, [sp, #16 * 15]
add sp, sp, #PT_REGS_SIZE
ic iallu
isb sy
.endm
.section .text.vectors

View File

@ -4,7 +4,7 @@ use alloc::{collections::BTreeMap, vec::Vec};
use device_api::interrupt::{IrqLevel, IrqOptions, IrqTrigger};
use device_tree::{
device_tree_driver,
dt::{self, DevTreeIndexNodeExt, DevTreeIndexPropExt, DevTreeNodeInfo},
dt::{self, DevTreeIndexNodeExt, DevTreeIndexPropExt},
};
use libk_mm::address::PhysicalAddress;
use ygg_driver_pci::{
@ -12,177 +12,178 @@ use ygg_driver_pci::{
PciAddress, PciAddressRange, PciBusManager, PciRangeType,
};
fn extract_ranges(dt: &DevTreeNodeInfo) -> Vec<PciAddressRange> {
let Some(ranges) = dt::find_prop(&dt.node, "ranges") else {
return Vec::new();
};
let pci_address_cells = dt.node.address_cells();
let pci_size_cells = dt.node.size_cells();
let cells_per_range = dt.address_cells + pci_address_cells + pci_size_cells;
assert_eq!(ranges.len() % cells_per_range, 0);
let range_count = ranges.len() / (cells_per_range * 4);
let mut result = Vec::new();
for i in 0..range_count {
let ty_bits = ranges.cell1_array_item(i * cells_per_range, 1).unwrap();
let ty = match (ty_bits >> 24) & 0x3 {
0 => PciRangeType::Configuration,
1 => PciRangeType::Io,
2 => PciRangeType::Memory32,
3 => PciRangeType::Memory64,
_ => unreachable!(),
};
let bus_number = (ty_bits >> 16) as u8;
let pci_base = match pci_address_cells {
3 => {
let hi = ranges.cell1_array_item(i * cells_per_range + 1, 1).unwrap();
let lo = ranges.cell1_array_item(i * cells_per_range + 2, 1).unwrap();
(hi << 32) | lo
}
_ => unimplemented!(),
};
let host_base = PhysicalAddress::from_u64(match dt.address_cells {
2 => {
let hi = ranges
.cell1_array_item(i * cells_per_range + pci_address_cells, 1)
.unwrap();
let lo = ranges
.cell1_array_item(i * cells_per_range + pci_address_cells + 1, 1)
.unwrap();
(hi << 32) | lo
}
_ => unimplemented!(),
});
let size = match pci_size_cells {
2 => {
let hi = ranges
.cell1_array_item(
i * cells_per_range + pci_address_cells + dt.address_cells,
1,
)
.unwrap();
let lo = ranges
.cell1_array_item(
i * cells_per_range + pci_address_cells + dt.address_cells + 1,
1,
)
.unwrap();
(hi << 32) | lo
}
_ => unimplemented!(),
} as usize;
result.push(PciAddressRange {
ty,
bus_number,
host_base,
pci_base,
size,
});
}
result
}
fn extract_interrupt_map(dt: &DevTreeNodeInfo) -> BTreeMap<PciInterrupt, PciInterruptRoute> {
// let interrupt_map_mask = devtree::find_prop(&dt.node, "interrupt-map").unwrap();
let interrupt_map = dt::find_prop(&dt.node, "interrupt-map").unwrap();
let pci_address_cells = dt.node.address_cells();
// TODO replace 3 with interrupt-cells in interrupt-controller
let cells_per_imap = pci_address_cells + /* Pin */ 1 + /* #interrupt-cells in interrupt-controller */ 3 + /* Interrupt Controller Data */ 3;
assert_eq!(interrupt_map.len() % (4 * cells_per_imap), 0);
let mut imap = BTreeMap::new();
for i in 0..interrupt_map.len() / (4 * cells_per_imap) {
let pci_address_0 = interrupt_map
.cell1_array_item(i * cells_per_imap, 1)
.unwrap();
let bus = (pci_address_0 >> 24) as u8;
let device = ((pci_address_0 >> 11) & 0x1F) as u8;
let function = ((pci_address_0 >> 8) & 0x7) as u8;
let address = PciAddress::for_function(0, bus, device, function);
let pin = interrupt_map
.cell1_array_item(i * cells_per_imap + pci_address_cells, 1)
.unwrap() as u32;
let Ok(pin) = PciInterruptPin::try_from(pin) else {
continue;
};
let _interrupt_ty = interrupt_map
.cell1_array_item(i * cells_per_imap + pci_address_cells + 4, 1)
.unwrap();
let interrupt_number = interrupt_map
.cell1_array_item(i * cells_per_imap + pci_address_cells + 5, 1)
.unwrap();
let interrupt_mode = interrupt_map
.cell1_array_item(i * cells_per_imap + pci_address_cells + 6, 1)
.unwrap();
let (trigger, level) = match interrupt_mode {
0x04 => (IrqTrigger::Level, IrqLevel::ActiveHigh),
_ => todo!(),
};
let src = PciInterrupt { address, pin };
let dst = PciInterruptRoute {
number: interrupt_number as _,
options: IrqOptions { trigger, level },
};
// TODO use phandle for interrupt-controller
// TODO interrupt-controller-specific decoding of idata
// TODO don't ignore interrupt_ty, don't assume they're all SPIs
imap.insert(src, dst);
}
imap
}
// fn extract_ranges(dt: &DevTreeNodeInfo) -> Vec<PciAddressRange> {
// let Some(ranges) = dt::find_prop(&dt.node, "ranges") else {
// return Vec::new();
// };
// let pci_address_cells = dt.node.address_cells();
// let pci_size_cells = dt.node.size_cells();
//
// let cells_per_range = dt.address_cells + pci_address_cells + pci_size_cells;
//
// assert_eq!(ranges.len() % cells_per_range, 0);
//
// let range_count = ranges.len() / (cells_per_range * 4);
//
// let mut result = Vec::new();
//
// for i in 0..range_count {
// let ty_bits = ranges.cell1_array_item(i * cells_per_range, 1).unwrap();
// let ty = match (ty_bits >> 24) & 0x3 {
// 0 => PciRangeType::Configuration,
// 1 => PciRangeType::Io,
// 2 => PciRangeType::Memory32,
// 3 => PciRangeType::Memory64,
// _ => unreachable!(),
// };
// let bus_number = (ty_bits >> 16) as u8;
//
// let pci_base = match pci_address_cells {
// 3 => {
// let hi = ranges.cell1_array_item(i * cells_per_range + 1, 1).unwrap();
// let lo = ranges.cell1_array_item(i * cells_per_range + 2, 1).unwrap();
//
// (hi << 32) | lo
// }
// _ => unimplemented!(),
// };
//
// let host_base = PhysicalAddress::from_u64(match dt.address_cells {
// 2 => {
// let hi = ranges
// .cell1_array_item(i * cells_per_range + pci_address_cells, 1)
// .unwrap();
// let lo = ranges
// .cell1_array_item(i * cells_per_range + pci_address_cells + 1, 1)
// .unwrap();
//
// (hi << 32) | lo
// }
// _ => unimplemented!(),
// });
//
// let size = match pci_size_cells {
// 2 => {
// let hi = ranges
// .cell1_array_item(
// i * cells_per_range + pci_address_cells + dt.address_cells,
// 1,
// )
// .unwrap();
// let lo = ranges
// .cell1_array_item(
// i * cells_per_range + pci_address_cells + dt.address_cells + 1,
// 1,
// )
// .unwrap();
//
// (hi << 32) | lo
// }
// _ => unimplemented!(),
// } as usize;
//
// result.push(PciAddressRange {
// ty,
// bus_number,
// host_base,
// pci_base,
// size,
// });
// }
//
// result
// }
//
// fn extract_interrupt_map(dt: &DevTreeNodeInfo) -> BTreeMap<PciInterrupt, PciInterruptRoute> {
// // let interrupt_map_mask = devtree::find_prop(&dt.node, "interrupt-map").unwrap();
// let interrupt_map = dt::find_prop(&dt.node, "interrupt-map").unwrap();
// let pci_address_cells = dt.node.address_cells();
//
// // TODO replace 3 with interrupt-cells in interrupt-controller
// let cells_per_imap = pci_address_cells + /* Pin */ 1 + /* #interrupt-cells in interrupt-controller */ 3 + /* Interrupt Controller Data */ 3;
//
// assert_eq!(interrupt_map.len() % (4 * cells_per_imap), 0);
//
// let mut imap = BTreeMap::new();
//
// for i in 0..interrupt_map.len() / (4 * cells_per_imap) {
// let pci_address_0 = interrupt_map
// .cell1_array_item(i * cells_per_imap, 1)
// .unwrap();
//
// let bus = (pci_address_0 >> 24) as u8;
// let device = ((pci_address_0 >> 11) & 0x1F) as u8;
// let function = ((pci_address_0 >> 8) & 0x7) as u8;
//
// let address = PciAddress::for_function(0, bus, device, function);
//
// let pin = interrupt_map
// .cell1_array_item(i * cells_per_imap + pci_address_cells, 1)
// .unwrap() as u32;
//
// let Ok(pin) = PciInterruptPin::try_from(pin) else {
// continue;
// };
//
// let _interrupt_ty = interrupt_map
// .cell1_array_item(i * cells_per_imap + pci_address_cells + 4, 1)
// .unwrap();
// let interrupt_number = interrupt_map
// .cell1_array_item(i * cells_per_imap + pci_address_cells + 5, 1)
// .unwrap();
// let interrupt_mode = interrupt_map
// .cell1_array_item(i * cells_per_imap + pci_address_cells + 6, 1)
// .unwrap();
//
// let (trigger, level) = match interrupt_mode {
// 0x04 => (IrqTrigger::Level, IrqLevel::ActiveHigh),
// _ => todo!(),
// };
//
// let src = PciInterrupt { address, pin };
// let dst = PciInterruptRoute {
// number: interrupt_number as _,
// options: IrqOptions { trigger, level },
// };
//
// // TODO use phandle for interrupt-controller
// // TODO interrupt-controller-specific decoding of idata
// // TODO don't ignore interrupt_ty, don't assume they're all SPIs
// imap.insert(src, dst);
// }
//
// imap
// }
device_tree_driver! {
compatible: ["pci-host-ecam-generic"],
probe(dt) => {
let reg = dt::find_prop(&dt.node, "reg")?;
let bus_range = dt::find_prop(&dt.node, "bus-range")?;
loop {}
// let reg = dt::find_prop(&dt.node, "reg")?;
// let bus_range = dt::find_prop(&dt.node, "bus-range")?;
let (cfg_space_base, _) = reg
.cell2_array_item(0, dt.address_cells, dt.size_cells)
.unwrap();
let cfg_space_base = PhysicalAddress::from_u64(cfg_space_base);
// let (cfg_space_base, _) = reg
// .cell2_array_item(0, dt.address_cells, dt.size_cells)
// .unwrap();
// let cfg_space_base = PhysicalAddress::from_u64(cfg_space_base);
let bus_start = bus_range.cell1_array_item(0, 1)? as u8;
let bus_end = bus_range.cell1_array_item(1, 1)? as u8;
// let bus_start = bus_range.cell1_array_item(0, 1)? as u8;
// let bus_end = bus_range.cell1_array_item(1, 1)? as u8;
let ranges = extract_ranges(dt);
let interrupt_map = extract_interrupt_map(dt);
// let ranges = extract_ranges(dt);
// let interrupt_map = extract_interrupt_map(dt);
if ranges.is_empty() {
return None;
}
// if ranges.is_empty() {
// return None;
// }
PciBusManager::add_segment_from_device_tree(
cfg_space_base,
bus_start..bus_end,
ranges,
interrupt_map
).ok();
// PciBusManager::add_segment_from_device_tree(
// cfg_space_base,
// bus_start..bus_end,
// ranges,
// interrupt_map
// ).ok();
None
// None
}
}

View File

@ -1,6 +1,6 @@
//! Bus devices
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
pub mod dt_pci;
// #[cfg(any(target_arch = "aarch64", rust_analyzer))]
// pub mod dt_pci;
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
pub mod simple_bus;

View File

@ -1,26 +1,95 @@
//! Simple "passthrough" bus device
use device_tree::{device_tree_driver, dt::DevTreeIndexNodeExt};
use libk::device::manager::DEVICE_REGISTRY;
// use device_tree::{device_tree_driver, dt::DevTreeIndexNodeExt};
// use libk::device::manager::DEVICE_REGISTRY;
use core::{
ops::Range,
sync::atomic::{AtomicBool, Ordering},
};
use abi::error::Error;
use alloc::{sync::Arc, vec::Vec};
use device_api::{bus::Bus, device::Device};
use device_tree::{
driver::{device_tree_driver, register_bus, Node},
dt::DevTreeIndexPropExt,
};
struct SimpleBus {
ranges: Vec<(Range<u64>, u64)>,
node: Node,
}
impl Device for SimpleBus {
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
Ok(())
}
fn display_name(&self) -> &str {
"simple-bus"
}
}
impl Bus for SimpleBus {
fn map_range(&self, bus_range: Range<u64>) -> Result<Range<u64>, Error> {
for (range, offset) in self.ranges.iter() {
if range.contains(&bus_range.start) {
let base = bus_range.start - range.start + *offset;
let size = bus_range.end - bus_range.start;
return Ok(base..base + size);
}
}
Err(Error::DoesNotExist)
}
fn enumerate(&self, handler: fn(Arc<dyn Device>)) {
log::info!("Enumerating simple-bus {:?}", self.node.name());
for child in self.node.children() {
if let Some(device) = child.probe() {
handler(device);
}
}
}
}
// TODO support nested busses?
// TODO better initialization sequence for device tree nodes
static SIMPLE_BUS_DISCOVERED: AtomicBool = AtomicBool::new(false);
device_tree_driver! {
compatible: ["simple-bus"],
probe(dt) => {
let address_cells = dt.node.address_cells();
let size_cells = dt.node.size_cells();
probe(of) => {
if SIMPLE_BUS_DISCOVERED.swap(true, Ordering::Acquire) {
return None;
}
let nodes = dt.node.children();
// Format per DT spec: (child-bus-address, parent-bus-address, length)
// Where:
// child-bus-address: #address-cells of this node
// parent-bus-address: #address-cells of parent bus
// length: #size-cells of this node
let ranges = of.property("ranges")?;
// Iterate devices on the bus
device_tree::driver::enumerate_dt(address_cells, size_cells, nodes, |_, probe| {
if let Some(device) = device_tree::driver::probe_dt_node(&probe) {
DEVICE_REGISTRY.add_pending_initialization(device, false);
}
let parent_address_cells = of.parent_address_cells();
let child_address_cells = of.self_address_cells()?;
let child_size_cells = of.self_size_cells()?;
Ok(())
}).ok();
let cell_sizes = (child_address_cells, parent_address_cells, child_size_cells);
// Don't yield any devices
None
let mut items = Vec::new();
for (child_address, parent_address, length) in ranges.iter_cells(cell_sizes) {
let child_range = child_address..child_address + length;
items.push((child_range, parent_address));
}
let bus = Arc::new(SimpleBus {
ranges: items,
node: of.clone(),
});
register_bus(bus.clone(), bus.ranges.iter().map(|(a, _)| a.clone()));
Some(bus)
}
}

View File

@ -1,24 +1,13 @@
//! Device management and interfaces
// use device_api::{manager::DeviceManager, Device, DeviceId};
// use libk_util::sync::{IrqSafeSpinlock, IrqSafeSpinlockGuard};
//
pub mod bus;
use alloc::string::String;
use libk_util::OneTimeInit;
pub mod bus;
pub mod display;
pub mod power;
pub mod serial;
// pub mod power;
// pub mod timer;
// static DEVICE_MANAGER: IrqSafeSpinlock<DeviceManager> = IrqSafeSpinlock::new(DeviceManager::new());
//
// /// Adds a device to the kernel's device table and returns the ID assigned to it
// pub fn register_device(device: &'static dyn Device) -> DeviceId {
// log::debug!("Register {:?}", device.display_name());
// DEVICE_MANAGER.lock().register(device)
// }
//
// /// Returns a safe reference to the kernel's [DeviceManager] instance
// pub fn manager_lock<'a>() -> IrqSafeSpinlockGuard<'a, DeviceManager> {
// DEVICE_MANAGER.lock()
// }
/// Generic machine description string
pub static MACHINE_NAME: OneTimeInit<String> = OneTimeInit::new();

View File

@ -72,22 +72,23 @@ impl Psci {
device_tree_driver! {
compatible: ["arm,psci-1.0", "arm,psci"],
probe(dt) => {
let method: &str = dt.node.prop("method")?;
let method = match method {
"hvc" => CallMethod::Hvc,
"smc" => CallMethod::Smc,
_ => panic!("Unknown PSCI call method: {:?}", method)
};
let cpu_on = dt.node.prop("cpu_on")?;
let cpu_off = dt.node.prop("cpu_off")?;
let cpu_suspend = dt.node.prop("cpu_suspend")?;
probe(of) => {
todo!()
// let method: &str = dt.node.prop("method")?;
// let method = match method {
// "hvc" => CallMethod::Hvc,
// "smc" => CallMethod::Smc,
// _ => panic!("Unknown PSCI call method: {:?}", method)
// };
// let cpu_on = dt.node.prop("cpu_on")?;
// let cpu_off = dt.node.prop("cpu_off")?;
// let cpu_suspend = dt.node.prop("cpu_suspend")?;
Some(Arc::new(Psci {
method,
cpu_on,
cpu_off,
cpu_suspend
}))
// Some(Arc::new(Psci {
// method,
// cpu_on,
// cpu_off,
// cpu_suspend
// }))
}
}

View File

@ -0,0 +1,222 @@
//! Broadcom BCM2835 mini-UART driver
// TODO
#![allow(missing_docs)]
use core::sync::atomic::{AtomicBool, Ordering};
use abi::{
error::Error,
io::{TerminalOptions, TerminalOutputOptions},
};
use alloc::sync::Arc;
use device_api::{
device::Device,
interrupt::{InterruptHandler, Irq, IrqLevel, IrqOptions, IrqTrigger},
};
use device_tree::driver::device_tree_driver;
use libk::{
debug::{DebugSink, LogLevel},
device::{external_interrupt_controller, manager::DEVICE_REGISTRY},
vfs::{Terminal, TerminalInput, TerminalOutput},
};
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite},
};
register_bitfields! {
u32,
AUX_MU_IER_REG [
RX_IRQ OFFSET(0) NUMBITS(1) [],
TX_IRQ OFFSET(1) NUMBITS(1) [],
],
AUX_MU_IIR_REG [
IID OFFSET(1) NUMBITS(2) [
None = 0,
TxEmpty = 1,
RxNotEmpty = 2,
],
PENDING OFFSET(0) NUMBITS(1) [],
],
AUX_MU_LSR_REG [
TX_EMPTY OFFSET(5) NUMBITS(1) [],
]
}
register_structs! {
#[allow(non_snake_case)]
Regs {
(0x00 => AUX_MU_IO_REG: ReadWrite<u32>),
(0x04 => AUX_MU_IER_REG: ReadWrite<u32, AUX_MU_IER_REG::Register>),
(0x08 => AUX_MU_IIR_REG: ReadWrite<u32, AUX_MU_IIR_REG::Register>),
(0x0C => _0),
(0x14 => AUX_MU_LSR_REG: ReadOnly<u32, AUX_MU_LSR_REG::Register>),
(0x18 => _1),
(0x30 => @END),
}
}
struct Inner {
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
}
pub struct Bcm2835AuxUart {
base: PhysicalAddress,
irq: Irq,
inner: OneTimeInit<Arc<Terminal<Inner>>>,
}
impl Regs {
fn write_byte(&self, byte: u8) -> Result<(), Error> {
while !self
.AUX_MU_LSR_REG
.matches_all(AUX_MU_LSR_REG::TX_EMPTY::SET)
{
core::hint::spin_loop();
}
self.AUX_MU_IO_REG.set(byte as u32);
Ok(())
}
fn write_bytes(&self, bytes: &[u8]) -> Result<(), Error> {
for &byte in bytes {
self.write_byte(byte)?;
}
Ok(())
}
}
impl TerminalOutput for Inner {
fn write(&self, byte: u8) -> Result<(), Error> {
self.regs.lock().write_byte(byte)
}
fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
self.regs.lock().write_bytes(bytes)?;
Ok(bytes.len())
}
}
impl DebugSink for Bcm2835AuxUart {
fn putc(&self, c: u8) -> Result<(), Error> {
self.inner.get().putc_to_output(c)
}
fn puts(&self, s: &str) -> Result<(), Error> {
self.inner.get().write_to_output(s.as_bytes())?;
Ok(())
}
fn supports_control_sequences(&self) -> bool {
true
}
}
impl InterruptHandler for Bcm2835AuxUart {
fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
let inner = self.inner.get();
let (status, byte) = {
let regs = inner.output().regs.lock();
// Reset IRQ
regs.AUX_MU_IIR_REG.modify(AUX_MU_IIR_REG::IID::SET);
let byte = regs.AUX_MU_IO_REG.get() as u8;
let status = regs
.AUX_MU_IIR_REG
.matches_all(AUX_MU_IIR_REG::PENDING::SET);
(status, byte)
};
if status {
inner.write_to_input(byte);
}
status
}
fn display_name(&self) -> &str {
"BCM283x mini-UART IRQ"
}
}
impl Device for Bcm2835AuxUart {
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
// TODO initialize clock
// TODO initialize pinctrl
// TODO initialize AUX_ENABLES
let regs = unsafe { DeviceMemoryIo::map(self.base, Default::default()) }?;
let config = TerminalOptions {
output: TerminalOutputOptions::NL_TO_CRNL,
..Default::default()
};
let output = Inner {
regs: IrqSafeSpinlock::new(regs),
};
let input = TerminalInput::with_capacity(64)?;
let inner = self
.inner
.init(Arc::new(Terminal::from_parts(config, input, output)));
DEVICE_REGISTRY
.serial_terminal
.register(inner.clone(), Some((self.clone(), LogLevel::Info)))
.ok();
Ok(())
}
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
let intc = external_interrupt_controller()?;
intc.register_irq(
self.irq,
IrqOptions {
trigger: IrqTrigger::Level,
level: IrqLevel::ActiveLow,
},
self.clone(),
)?;
intc.enable_irq(self.irq)?;
let inner = self.inner.get().output();
let regs = inner.regs.lock();
regs.AUX_MU_IER_REG
.modify(AUX_MU_IER_REG::RX_IRQ::SET + AUX_MU_IER_REG::TX_IRQ::CLEAR);
Ok(())
}
fn display_name(&self) -> &str {
"bcm2835 mini-UART"
}
}
// TODO handle interrupt-parent
// TODO handle pinctrl
// TODO handle clock
// TODO fix issue with duplicate initialization
static AUX_UART_DISCOVERED: AtomicBool = AtomicBool::new(false);
device_tree_driver! {
compatible: ["brcm,bcm2835-aux-uart"],
probe(of) => {
if AUX_UART_DISCOVERED.swap(true, Ordering::Relaxed) {
return None;
}
let range = of.mapped_range(0)?;
let base = PhysicalAddress::from_u64(range.start);
let irq = Irq::External(93);
Some(Arc::new(Bcm2835AuxUart {
base,
irq,
inner: OneTimeInit::new()
}))
}
}

View File

@ -1,4 +1,12 @@
//! Serial device interfaces
// Raspberry Pi
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
pub mod bcm2835_aux_uart;
// TODO temporarily disabled for raspi4b
#[cfg(any(
all(target_arch = "aarch64", not(feature = "aarch64_board_raspi4b")),
rust_analyzer
))]
pub mod pl011;

View File

@ -1,11 +1,13 @@
//! ARM PL011 driver
use core::sync::atomic::{AtomicBool, Ordering};
use abi::{error::Error, io::TerminalOptions};
use alloc::sync::Arc;
use device_api::{
device::Device,
interrupt::{InterruptHandler, Irq},
};
use device_tree::{device_tree_driver, dt::DevTreeIndexPropExt};
use device_tree::driver::device_tree_driver;
use libk::{
debug::{DebugSink, LogLevel},
device::{external_interrupt_controller, manager::DEVICE_REGISTRY},
@ -162,7 +164,7 @@ impl Device for Pl011 {
DEVICE_REGISTRY
.serial_terminal
.register(terminal.clone(), Some((self.clone(), LogLevel::Debug)))
.register(terminal.clone(), Some((self.clone(), LogLevel::Info)))
.ok();
Ok(())
@ -182,17 +184,22 @@ impl Device for Pl011 {
}
}
// TODO prevent double initialization
static PL011_DISCOVERED: AtomicBool = AtomicBool::new(false);
device_tree_driver! {
compatible: ["arm,pl011"],
probe(of) => {
let reg = device_tree::find_prop(&of.node, "reg")?;
let (base, _) = reg.cell2_array_item(0, of.address_cells, of.size_cells)?;
if PL011_DISCOVERED.swap(true, Ordering::Acquire) {
return None;
}
let range = of.mapped_range(0)?;
let base = PhysicalAddress::from_u64(range.start);
let irq = Irq::External(1);
Some(Arc::new(Pl011 {
inner: OneTimeInit::new(),
// TODO obtain IRQ from dt
irq: Irq::External(1),
base: PhysicalAddress::from_u64(base)
base,
irq,
inner: OneTimeInit::new()
}))
}
}

View File

@ -105,6 +105,7 @@ pub fn init() {
("mem", d_mem),
("proc", d_proc),
("arch", const_value_node(util::arch_str())),
("machine", const_value_node(util::machine_name())),
]);
ROOT.init(root);

View File

@ -16,8 +16,8 @@ use crate::{
};
fn setup_root() -> Result<NodeRef, Error> {
let initrd_data = INITRD_DATA.get();
let fs = MemoryFilesystem::<FileBlockAllocator>::from_slice(initrd_data.data).unwrap();
let initrd_data = INITRD_DATA.try_get().ok_or(Error::DoesNotExist)?;
let fs = MemoryFilesystem::<FileBlockAllocator>::from_slice(initrd_data.data)?;
fs.root()
}
@ -52,7 +52,9 @@ pub fn kinit() -> Result<(), Error> {
random::init();
let root = setup_root()?;
let root = setup_root().inspect_err(|error| {
log::error!("Cannot setup root filesystem: {error:?}");
})?;
let mut ioctx = IoContext::new(root);

View File

@ -87,6 +87,7 @@ pub fn kernel_secondary_main() -> ! {
/// * Heap
/// * Basic debugging facilities
/// * Initrd
#[inline(never)]
pub fn kernel_main() -> ! {
log::info!(
"Yggdrasil v{} ({})",
@ -96,16 +97,16 @@ pub fn kernel_main() -> ! {
libk::panic::set_handler(panic::panic_handler);
// Setup the sysfs
sysfs::init();
fs::add_pseudo_devices().unwrap();
unsafe {
PLATFORM.start_application_processors();
}
Cpu::init_ipi_queues(ArchitectureImpl::cpu_count());
// Setup the sysfs
sysfs::init();
fs::add_pseudo_devices().unwrap();
// Wait until all APs initialize
CPU_INIT_FENCE.signal();
CPU_INIT_FENCE.wait_all(ArchitectureImpl::cpu_count());

View File

@ -1,5 +1,7 @@
//! Various kernel utility functions
use crate::device::MACHINE_NAME;
/// Extension trait for [Iterator]s of [Result]s
pub trait ResultIterator<T, E> {
/// Drops entries from the iterator until the first error
@ -55,3 +57,42 @@ pub const fn arch_str() -> &'static str {
"i686"
}
}
/// Returns a generic machine description string
pub fn machine_name() -> &'static str {
if let Some(machine) = MACHINE_NAME.try_get() {
machine.as_str()
} else {
"unknown"
}
}
/// Invokes the functions defined in .init_array
///
/// # Safety
///
/// Must only be called once after basic memory management was initialized, but before
/// platform device enumeration.
pub unsafe fn call_init_array() {
type InitFn = extern "C" fn();
extern "C" {
static __init_array_start: u8;
static __init_array_end: u8;
}
// TODO provide some warning if .init_array size is misaligned
let base = &raw const __init_array_start;
let end = (&raw const __init_array_end).addr();
if end <= base.addr() {
return;
}
let size = (end - base.addr()) / size_of::<InitFn>();
let init_array: &[InitFn] = core::slice::from_raw_parts(base.cast(), size);
for function in init_array {
function();
}
}

View File

@ -2,7 +2,7 @@
#[macro_export]
macro_rules! primitive_enum {
($(#[$struct_meta:meta])* $vis:vis enum $name:ident: $repr:ty {
$( $(#[$variant_meta:meta])* $variant:ident = $discriminant:literal, )+
$( $(#[$variant_meta:meta])* $variant:ident = $discriminant:literal ),+ $(,)?
}) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr($repr)]

View File

@ -5,6 +5,7 @@ use crate::{Architecture, IntoArgs};
#[derive(Debug)]
pub enum Machine {
Virt { virtualize: bool },
Raspi4b,
}
#[derive(Debug)]
@ -35,6 +36,9 @@ impl IntoArgs for Machine {
}
command.arg(arg);
}
&Self::Raspi4b => {
command.arg("raspi4b");
}
}
}
}

View File

@ -31,6 +31,7 @@ pub enum QemuDevice {
pub enum QemuSerialTarget {
MonStdio,
Stdio,
None,
}
impl IntoArgs for QemuNic {
@ -126,6 +127,9 @@ impl IntoArgs for QemuSerialTarget {
Self::Stdio => {
command.arg("stdio");
}
Self::None => {
command.arg("none");
}
}
}
}

View File

@ -37,7 +37,7 @@ impl<T: IntoArgs> IntoArgs for Option<T> {
#[derive(Debug, Default)]
struct QemuCommon {
devices: Vec<QemuDevice>,
serial: Option<QemuSerialTarget>,
serial: Vec<QemuSerialTarget>,
}
#[derive(Debug, Default)]
@ -110,7 +110,7 @@ impl<A: Architecture> Qemu<A> {
}
pub fn with_serial(&mut self, serial: QemuSerialTarget) -> &mut Self {
self.common.serial = Some(serial);
self.common.serial.push(serial);
self
}
@ -166,7 +166,9 @@ impl<A: Architecture> Qemu<A> {
impl IntoArgs for QemuCommon {
fn add_args(&self, command: &mut Command) {
self.serial.add_args(command);
for serial in self.serial.iter() {
serial.add_args(command);
}
}
}

View File

@ -1,2 +1,28 @@
use std::{fs::File, io::{stdout, Read, Write}};
fn main() {
let mut buffer = [0; 32];
{
let mut file = File::open("/sys/kernel/attr3").unwrap();
let len = file.read(&mut buffer).unwrap();
println!("Current value:");
stdout().write_all(&buffer[..len]).ok();
println!();
}
{
let mut file = File::create("/sys/kernel/attr3").unwrap();
file.write_all(b"abcdef").unwrap();
}
{
let mut file = File::open("/sys/kernel/attr3").unwrap();
let len = file.read(&mut buffer).unwrap();
println!("New value:");
stdout().write_all(&buffer[..len]).ok();
println!();
}
}

View File

@ -1,45 +1,38 @@
use std::{
collections::HashMap,
ffi::OsStr,
path::{Path, PathBuf},
process::Command,
};
use serde::Deserialize;
use std::{ffi::OsStr, path::Path, process::Command};
use crate::{
env::{Arch, BuildEnv, Profile},
env::{Arch, Board, BuildEnv, Profile},
error::Error,
};
#[derive(Debug, Clone, Deserialize)]
pub struct ModuleDependency {
pub version: Option<semver::VersionReq>,
pub path: PathBuf,
}
// #[derive(Debug, Clone, Deserialize)]
// pub struct ModuleDependency {
// pub version: Option<semver::VersionReq>,
// pub path: PathBuf,
// }
#[derive(Debug, Deserialize)]
pub struct ModuleManifest {
pub name: String,
pub version: semver::Version,
#[serde(default)]
pub dependencies: HashMap<String, ModuleDependency>,
}
// #[derive(Debug, Deserialize)]
// pub struct ModuleManifest {
// pub name: String,
// pub version: semver::Version,
// #[serde(default)]
// pub dependencies: HashMap<String, ModuleDependency>,
// }
#[derive(Debug)]
pub struct ModuleInfo {
pub dependencies: Vec<(String, String, ModuleDependency)>,
pub module_dir_name: PathBuf,
pub manifest_dir: PathBuf,
pub manifest: ModuleManifest,
}
// #[derive(Debug)]
// pub struct ModuleInfo {
// pub dependencies: Vec<(String, String, ModuleDependency)>,
// pub module_dir_name: PathBuf,
// pub manifest_dir: PathBuf,
// pub manifest: ModuleManifest,
// }
pub enum CargoBuilder<'e> {
Host(bool),
Userspace(&'e BuildEnv),
Ygglibc(&'e BuildEnv),
Kernel(&'e BuildEnv),
Module(&'e BuildEnv, &'e ModuleInfo),
// Module(&'e BuildEnv, &'e ModuleInfo),
}
impl<'e> CargoBuilder<'e> {
@ -118,9 +111,9 @@ impl<'e> CargoBuilder<'e> {
command.env(
"RUSTFLAGS",
format!(
"-C link-arg=-T{}/etc/{}.ld",
"-C link-arg=-T{}/etc/ld/{}",
env.workspace_root.display(),
env.arch.kernel_triple()
env.kernel_linker_script,
),
);
command.arg("+nightly").arg(arg);
@ -132,82 +125,86 @@ impl<'e> CargoBuilder<'e> {
let target_spec_arg = format!(
"--target={}/etc/{}.json",
env.workspace_root.display(),
env.arch.kernel_triple()
env.kernel_triple
);
command.arg(target_spec_arg);
command.args(["-Z", "build-std=core,alloc,compiler_builtins"]);
if env.profile == Profile::Release {
command.arg("--release");
}
}
Self::Module(env, module) => {
let mut lib_paths = vec![];
let mut extern_libs = vec![];
let mut dependency_list = String::new();
for (i, (name, dep)) in module.manifest.dependencies.iter().enumerate() {
if i != 0 {
dependency_list.push(':');
}
let dep_path = module.manifest_dir.join(&dep.path).canonicalize()?;
let output_dir = dep_path.join(format!(
"target/{}/{}",
env.arch.kernel_triple(),
env.profile.name()
));
let dependency_so = output_dir.join(format!("lib{}.so", name));
lib_paths.push(output_dir.join("deps"));
lib_paths.push(output_dir.clone());
lib_paths.push(dep_path.join(format!("target/{}/deps", env.profile.name())));
dependency_list.push_str(&format!("{}={}", name, dependency_so.display()));
extern_libs.push((name.clone(), dependency_so));
}
lib_paths.push(env.kernel_output_dir.join("deps"));
lib_paths.push(env.kernel_output_dir.clone());
lib_paths.push(
env.workspace_root
.join(format!("target/{}/deps", env.profile.name())),
);
extern_libs.push(("libk".into(), env.kernel_output_dir.join("liblibk.so")));
let mut rust_flags = Vec::new();
for lib_path in lib_paths {
rust_flags.push(format!("-L{}", lib_path.display()));
}
for (lib, path) in extern_libs {
rust_flags.push(format!("--extern {}={}", lib, path.display()));
}
rust_flags.push("-Cprefer-dynamic".into());
command.env("RUSTFLAGS", rust_flags.join(" "));
command.env("MOD_DEPENDENCIES", dependency_list);
command.env("MOD_LIBK_SO", env.kernel_output_dir.join("liblibk.so"));
command.arg("+nightly").arg(arg);
if env.verbose {
command.arg("-v");
}
let target_spec_arg = format!(
"--target={}/etc/{}.json",
env.workspace_root.display(),
env.arch.kernel_triple()
);
command.arg(target_spec_arg);
match env.board {
Board::virt | Board::default => command.arg("--features=aarch64_board_virt"),
Board::raspi4b => command.arg("--features=aarch64_board_raspi4b"),
};
if env.profile == Profile::Release {
command.arg("--release");
}
}
} // Self::Module(env, module) => {
// let mut lib_paths = vec![];
// let mut extern_libs = vec![];
// let mut dependency_list = String::new();
// for (i, (name, dep)) in module.manifest.dependencies.iter().enumerate() {
// if i != 0 {
// dependency_list.push(':');
// }
// let dep_path = module.manifest_dir.join(&dep.path).canonicalize()?;
// let output_dir = dep_path.join(format!(
// "target/{}/{}",
// env.kernel_triple,
// env.profile.name()
// ));
// let dependency_so = output_dir.join(format!("lib{}.so", name));
// lib_paths.push(output_dir.join("deps"));
// lib_paths.push(output_dir.clone());
// lib_paths.push(dep_path.join(format!("target/{}/deps", env.profile.name())));
// dependency_list.push_str(&format!("{}={}", name, dependency_so.display()));
// extern_libs.push((name.clone(), dependency_so));
// }
// lib_paths.push(env.kernel_output_dir.join("deps"));
// lib_paths.push(env.kernel_output_dir.clone());
// lib_paths.push(
// env.workspace_root
// .join(format!("target/{}/deps", env.profile.name())),
// );
// extern_libs.push(("libk".into(), env.kernel_output_dir.join("liblibk.so")));
// let mut rust_flags = Vec::new();
// for lib_path in lib_paths {
// rust_flags.push(format!("-L{}", lib_path.display()));
// }
// for (lib, path) in extern_libs {
// rust_flags.push(format!("--extern {}={}", lib, path.display()));
// }
// rust_flags.push("-Cprefer-dynamic".into());
// command.env("RUSTFLAGS", rust_flags.join(" "));
// command.env("MOD_DEPENDENCIES", dependency_list);
// command.env("MOD_LIBK_SO", env.kernel_output_dir.join("liblibk.so"));
// command.arg("+nightly").arg(arg);
// if env.verbose {
// command.arg("-v");
// }
// let target_spec_arg = format!(
// "--target={}/etc/{}.json",
// env.workspace_root.display(),
// env.kernel_triple
// );
// command.arg(target_spec_arg);
// if env.profile == Profile::Release {
// command.arg("--release");
// }
// }
}
log::trace!("Run cargo: {:?}", command);

View File

@ -4,7 +4,7 @@ use std::{
};
use crate::{
build::{cargo::CargoBuilder, module::build_modules},
build::cargo::CargoBuilder,
check::{self, AllOk},
env::{Arch, BuildEnv},
error::Error,
@ -15,7 +15,7 @@ pub mod x86_64;
pub mod c;
mod cargo;
mod module;
// mod module;
pub mod toolchain;
pub mod toolchain_c;
mod userspace;
@ -77,34 +77,35 @@ pub fn generate_kernel_tables(
}
}
pub fn build_all(env: BuildEnv) -> Result<AllBuilt, Error> {
pub fn build_all(env: &BuildEnv) -> Result<AllBuilt, Error> {
log::debug!("Environment: {:#?}", env);
let check = check::check_build_env(env.arch)?;
// Kernel stuff
let tools = build_kernel_tools(&env, check)?;
let kernel = build_kernel(&env, check)?;
let tools = build_kernel_tools(env, check)?;
let kernel = build_kernel(env, check)?;
let kernel = generate_kernel_tables(&env.kernel_symbol_file, kernel, tools)?;
let modules = match env.arch {
Arch::i686 => Vec::new(),
_ => build_modules(&env, env.workspace_root.join("kernel/modules"))?,
};
// let modules = match env.arch {
// Arch::i686 => Vec::new(),
// _ => build_modules(&env, env.workspace_root.join("kernel/modules"))?,
// };
let mut install_extra = vec![];
for module in modules {
install_extra.push((module.clone(), module.file_name().unwrap().into()));
}
// let mut install_extra = vec![];
let install_extra = vec![];
// for module in modules {
// install_extra.push((module.clone(), module.file_name().unwrap().into()));
// }
// Userspace stuff
let initrd = userspace::build_initrd(&env, install_extra, check)?;
let initrd = userspace::build_initrd(env, install_extra, check)?;
// Build target-specific image
let image = match env.arch {
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)?),
Arch::x86_64 => AllBuilt::X86_64(x86_64::build_image(env, kernel, initrd)?),
Arch::i686 => AllBuilt::I686(i686::build_image(env, kernel, initrd)?),
};
Ok(image)

View File

@ -66,6 +66,18 @@ pub enum Arch {
i686,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, ValueEnum)]
#[allow(non_camel_case_types)]
#[clap(rename_all = "verbatim")]
pub enum Board {
#[default]
default,
// AArch64 boards
raspi4b,
virt,
}
#[derive(Debug)]
pub struct BuildEnv {
pub config: XTaskConfig,
@ -73,6 +85,9 @@ pub struct BuildEnv {
pub profile: Profile,
pub arch: Arch,
pub board: Board,
pub kernel_triple: &'static str,
pub kernel_linker_script: String,
pub host_triple: String,
pub workspace_root: PathBuf,
@ -107,10 +122,25 @@ impl BuildEnv {
verbose: bool,
profile: Profile,
arch: Arch,
board: Board,
workspace_root: PathBuf,
) -> Self {
let kernel_triple = match (arch, board) {
(Arch::aarch64, Board::virt | Board::default) => "aarch64-unknown-qemu",
(Arch::aarch64, Board::raspi4b) => "aarch64-unknown-raspi4b",
(Arch::x86_64, Board::default) => "x86_64-unknown-none",
(Arch::i686, Board::default) => "x86_64-unknown-none",
_ => {
log::error!("Invalid arch/board combination: {arch:?}/{board:?}");
panic!();
}
};
let kernel_linker_script = match arch {
Arch::aarch64 => format!("arm/{kernel_triple}.ld"),
Arch::i686 | Arch::x86_64 => format!("x86/{kernel_triple}.ld"),
};
let kernel_output_dir =
workspace_root.join(format!("target/{}/{}", arch.kernel_triple(), profile.dir()));
workspace_root.join(format!("target/{}/{}", kernel_triple, profile.dir()));
let userspace_output_dir = workspace_root.join(format!(
"userspace/target/{}-unknown-yggdrasil/{}",
arch.name(),
@ -130,6 +160,9 @@ impl BuildEnv {
profile,
arch,
board,
kernel_triple,
kernel_linker_script,
host_triple: host.into(),
workspace_root,
@ -167,14 +200,6 @@ impl Profile {
}
impl Arch {
pub fn kernel_triple(&self) -> &str {
match self {
Self::aarch64 => "aarch64-unknown-qemu",
Self::x86_64 => "x86_64-unknown-none",
Self::i686 => "i686-unknown-none",
}
}
pub fn user_triple(&self) -> &str {
match self {
Self::aarch64 => "aarch64-unknown-yggdrasil",

View File

@ -5,7 +5,7 @@ use std::{fs, path::PathBuf, process::ExitCode};
use build::CheckAction;
use clap::{Parser, Subcommand};
use env::{Arch, Profile, XTaskConfig};
use env::{Arch, Board, Profile, XTaskConfig};
use error::Error;
#[macro_use]
@ -30,6 +30,8 @@ struct Args {
help = "Target architecture"
)]
arch: Arch,
#[clap(short, long, env, default_value_t, value_enum, help = "Target board")]
board: Board,
#[clap(
short,
long,
@ -111,14 +113,21 @@ fn run(args: Args) -> Result<(), Error> {
} else {
XTaskConfig::default()
};
let env = env::BuildEnv::new(config, args.verbose, profile, args.arch, workspace_root);
let env = env::BuildEnv::new(
config,
args.verbose,
profile,
args.arch,
args.board,
workspace_root,
);
if args.clean_userspace && !matches!(&action, SubArgs::Clean { .. }) {
build::clean_userspace(&env)?;
}
match action {
SubArgs::Build => build::build_all(env).map(|_| ()),
SubArgs::Build => build::build_all(&env).map(|_| ()),
SubArgs::Check => build::check_all(env, CheckAction::Check),
SubArgs::Clippy => build::check_all(env, CheckAction::Clippy),
SubArgs::Test => build::test_all(env),

View File

@ -11,7 +11,7 @@ use qemu::{
use crate::{
build::{self, AllBuilt, ImageBuilt, InitrdGenerated, KernelBuilt, KernelProcessed},
env::BuildEnv,
env::{Board, BuildEnv},
error::Error,
util::run_external_command,
};
@ -148,6 +148,7 @@ fn make_kernel_bin<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> Result<(),
fn run_aarch64(
config: &QemuConfig,
env: &BuildEnv,
qemu_bin: Option<PathBuf>,
devices: Vec<QemuDevice>,
kernel_bin: PathBuf,
@ -157,22 +158,46 @@ fn run_aarch64(
if let Some(qemu_bin) = qemu_bin {
qemu.override_qemu(qemu_bin);
}
qemu.with_serial(QemuSerialTarget::MonStdio)
.with_cpu(aarch64::Cpu::Max)
.with_smp(config.machine.smp)
.with_machine(aarch64::Machine::Virt { virtualize: true })
qemu.with_smp(config.machine.smp)
.with_boot_image(aarch64::Image::Kernel {
kernel: kernel_bin,
initrd: Some(initrd),
})
.with_memory_megabytes(config.machine.aarch64.memory);
// .disable_display();
.disable_display();
for device in devices {
qemu.with_device(device);
match env.board {
Board::raspi4b => qemu
.with_machine(aarch64::Machine::Raspi4b)
.with_serial(QemuSerialTarget::None)
.with_serial(QemuSerialTarget::MonStdio),
Board::virt | Board::default => qemu
.with_machine(aarch64::Machine::Virt { virtualize: true })
.with_serial(QemuSerialTarget::MonStdio)
.with_cpu(aarch64::Cpu::Max)
.with_memory_megabytes(config.machine.aarch64.memory),
};
if env.board != Board::raspi4b {
for device in devices {
qemu.with_device(device);
}
}
Ok(qemu.into_command())
let mut command = qemu.into_command();
if env.board == Board::raspi4b {
// Provide the dtb
command.args([
"-dtb",
&format!(
"{}/etc/dtb/bcm2711-rpi-4-b.dtb",
env.workspace_root.display()
),
]);
}
Ok(command)
}
fn run_x86_64(
@ -277,7 +302,7 @@ pub fn run(
let kernel_output_dir = env.kernel_output_dir.clone();
let kernel_bin = kernel_output_dir.join("kernel.bin");
// Rebuild the image
let built = build::build_all(env)?;
let built = build::build_all(&env)?;
let mut devices = vec![];
add_devices_from_config(&mut devices, disk.as_ref(), &config)?;
@ -285,7 +310,7 @@ pub fn run(
let mut command = match built {
AllBuilt::AArch64(KernelProcessed(KernelBuilt(kernel)), InitrdGenerated(initrd)) => {
make_kernel_bin(kernel, &kernel_bin)?;
run_aarch64(&config, qemu, devices, kernel_bin, initrd)?
run_aarch64(&config, &env, qemu, devices, kernel_bin, initrd)?
}
AllBuilt::X86_64(ImageBuilt(image)) => run_x86_64(&config, qemu, devices, image)?,
AllBuilt::I686(ImageBuilt(image)) => run_i686(&config, qemu, devices, image)?,
@ -294,6 +319,7 @@ pub fn run(
command.args(extra_args);
log::trace!("Run QEMU: {:?}", command);
log::info!("Start qemu");
command.status()?;