dev: use device tree instead of hardcoded board impls

This commit is contained in:
Mark Poliakov 2023-08-13 21:23:58 +03:00
parent 37e6fc098b
commit e694c1cef0
48 changed files with 1656 additions and 732 deletions

@ -10,6 +10,7 @@ build = "build.rs"
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
vfs = { path = "lib/vfs" }
memfs = { path = "lib/memfs" }
device-api = { path = "lib/device-api", features = ["derive"] }
atomic_enum = "0.2.0"
bitflags = "2.3.3"
@ -45,3 +46,4 @@ default = []
fb_console = ["bitmap-font", "embedded-graphics"]
aarch64_qemu = []
aarch64_orange_pi3 = []
aarch64_rpi3b = []

14
lib/device-api/Cargo.toml Normal file

@ -0,0 +1,14 @@
[package]
name = "device-api"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
macros = { path = "macros", optional = true }
[features]
default = []
derive = ["macros"]

@ -0,0 +1,14 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.66"
quote = "1.0.32"
syn = { version = "2.0.28", features = ["full"] }

@ -0,0 +1,7 @@
use yggdrasil_abi::error::Error;
use crate::manager::DeviceManager;
pub trait Bus {
fn enumerate(&self, manager: &mut DeviceManager) -> Result<(), Error>;
}

@ -0,0 +1,37 @@
use yggdrasil_abi::error::Error;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[repr(transparent)]
pub struct DeviceId(u64);
pub trait Device: Send + 'static {
fn display_name(&self) -> &'static str;
/// Initializes the device, making it ready for operation.
/// The method is also responsible for registering the device with appropriate OS subsystems
/// (e.g. registering a terminal ttySn for a serial port)
///
/// # Safety
///
/// The caller must make sure the function is only called once.
unsafe fn init(&'static self) -> Result<(), Error> {
Ok(())
}
/// Initializes the IRQ handling options on this device: binds its IRQ(s) to their handlers and
/// enables their reception.
///
/// # Safety
///
/// The caller must make sure the function is only called once. The caller must also make sure
/// the function is not called before the device's [Device::init] is called.
unsafe fn init_irq(&'static self) -> Result<(), Error> {
Ok(())
}
}
impl From<usize> for DeviceId {
fn from(value: usize) -> Self {
Self(value as u64)
}
}

@ -0,0 +1,116 @@
use yggdrasil_abi::error::Error;
use crate::Device;
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum IrqLevel {
#[default]
Default,
ActiveHigh,
ActiveLow,
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum IrqTrigger {
#[default]
Default,
Edge,
Level,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IpiDeliveryTarget {
Specific(usize),
ThisCpu,
OtherCpus,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IrqNumber {
Private(u32),
Shared(u32),
}
#[derive(Default, Clone, Copy, Debug)]
pub struct IrqOptions {
pub level: IrqLevel,
pub trigger: IrqTrigger,
}
pub trait InterruptTable {
fn handler(&self, index: usize) -> Option<&'static dyn InterruptHandler>;
}
pub trait ExternalInterruptController {
/// Performs IRQ delivery method configuration and registers a handler to execute when it is
/// fired
fn register_irq(
&self,
irq: IrqNumber,
options: IrqOptions,
handler: &'static dyn InterruptHandler,
) -> Result<(), Error>;
/// Enables the specified IRQ (unmasks it)
fn enable_irq(&self, irq: IrqNumber) -> Result<(), Error>;
/// Handles a single pending interrupt on this controller.
/// The function is intended for interrupt controllers which have internal registers to track
/// the IRQ index and the order of interrupt handling (if multiple are handled in sequence) is
/// platform/controller specific.
fn handle_pending_irqs(&self) {}
/// Handles a single pending interrupt with a known index on this controller.
/// The function is intended for interrupt controllers where vectors "know" their interrupt
/// index.
fn handle_specific_irq(&self, #[allow(unused)] index: usize) {}
}
pub trait LocalInterruptController {
type IpiMessage;
fn send_ipi(&self, target: IpiDeliveryTarget, msg: Self::IpiMessage) -> Result<(), Error>;
/// Initializes the local interrupt controller for an Application Processor instance.
///
/// # Safety
///
/// The caller must ensure this function is only called once per each AP (and only for APs).
unsafe fn init_ap(&self) -> Result<(), Error>;
}
pub trait InterruptHandler: Device {
fn handle_irq(&self) -> bool;
}
pub struct FixedInterruptTable<const SIZE: usize> {
entries: [Option<&'static dyn InterruptHandler>; SIZE],
}
impl<const SIZE: usize> FixedInterruptTable<SIZE> {
pub const fn new() -> Self {
Self {
entries: [None; SIZE],
}
}
pub fn insert(
&mut self,
index: usize,
handler: &'static dyn InterruptHandler,
) -> Result<(), Error> {
if self.entries[index].is_some() {
todo!();
}
self.entries[index] = Some(handler);
Ok(())
}
}
impl<const SIZE: usize> InterruptTable for FixedInterruptTable<SIZE> {
fn handler(&self, index: usize) -> Option<&'static dyn InterruptHandler> {
self.entries[index]
}
}

24
lib/device-api/src/lib.rs Normal file

@ -0,0 +1,24 @@
#![no_std]
extern crate alloc;
pub mod bus;
pub mod device;
pub mod interrupt;
pub mod manager;
pub mod serial;
pub mod timer;
pub use device::{Device, DeviceId};
use yggdrasil_abi::error::Error;
pub trait CpuBringupDevice: Device {
/// Starts a CPU with given index, providing it with some argument value and instruction
/// pointer from which its execution should begin.
///
/// # Safety
///
/// This function is unsafe because it can have unexpected effects on the system state if
/// misused.
unsafe fn start_cpu(&self, id: usize, ip: usize, arg0: usize) -> Result<(), Error>;
}

@ -0,0 +1,25 @@
use alloc::vec::Vec;
use crate::{Device, DeviceId};
pub struct DeviceManager {
devices: Vec<&'static dyn Device>,
}
impl DeviceManager {
pub const fn new() -> Self {
Self {
devices: Vec::new(),
}
}
pub fn register(&mut self, device: &'static dyn Device) -> DeviceId {
let id = DeviceId::from(self.devices.len());
self.devices.push(device);
id
}
pub fn devices(&self) -> impl Iterator<Item = &'static dyn Device> + '_ {
self.devices.iter().copied()
}
}

@ -0,0 +1,7 @@
use yggdrasil_abi::error::Error;
use crate::Device;
pub trait SerialDevice: Device {
fn send(&self, byte: u8) -> Result<(), Error>;
}

@ -0,0 +1,31 @@
//! Interfaces for time-providing devices
use core::time::Duration;
use yggdrasil_abi::error::Error;
use crate::Device;
/// Interface for precise timing devices
pub trait MonotonicTimestampProviderDevice: Device {
/// Provides a timestamp value of the timer. The value:
///
/// * Represents monotonically increasing clock time since some arbitrary point in the past.
/// * Can be used for delays and measuring time passed between two measurements.
///
/// * Is not an accurate wall-clock time or real-world time.
/// * Cannot be used for date/time managament purposes.
fn monotonic_timestamp(&self) -> Result<Duration, Error>;
}
/// Interface for real-world time-telling devices
pub trait RealTimeProviderDevice: Device {
/// Provides a real-time clock value of the timer. The value:
///
/// * Represents a real-world time since TODO TODO TODO.
/// * Can be used for rough measurements of duration passed between two points in time.
/// * Can be used for delays, but precision is not guaranteed.
/// * Can be used for date/time management.
// TODO actual type for time
fn real_timestamp(&self) -> Result<u64, Error>;
}

@ -13,7 +13,32 @@
movk \reg, #((\value) >> 16), lsl #16
.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
.section .text.entry
__aarch64_entry:
// x0 -- dtb_phys
@ -30,26 +55,7 @@ __aarch64_entry:
// Leave EL2
.el2:
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, .el1
msr ELR_EL2, x8
isb
eret
LEAVE_EL2 .el1
.el1:
dsb sy
isb
@ -75,3 +81,24 @@ __aarch64_entry:
// TODO spin loop for this method of init
1:
b .
.section .text
__aarch64_ap_entry:
// x0 -- stack pointer (lower address space)
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
bl {kernel_ap_lower_entry} - {kernel_virt_offset}
b .

@ -7,20 +7,15 @@ use core::{
use aarch64_cpu::{asm::barrier, registers::CPACR_EL1};
use tock_registers::interfaces::ReadWriteable;
use super::{
cpu::Cpu, exception, kernel_main, smp::CPU_COUNT, AArch64, KernelStack, ARCHITECTURE,
BOOT_STACK_SIZE,
};
use super::{cpu::Cpu, exception, kernel_main, KernelStack, ARCHITECTURE, BOOT_STACK_SIZE};
use crate::{
absolute_address,
arch::{Architecture, PLATFORM},
device::platform::Platform,
arch::{aarch64::smp::CPU_COUNT, Architecture, ArchitectureImpl},
mem::{ConvertAddress, KERNEL_VIRT_OFFSET},
sync::SpinFence,
task,
};
#[allow(dead_code)]
pub(super) static CPU_INIT_FENCE: SpinFence = SpinFence::new();
extern "C" fn el1_bsp_lower_entry(dtb_phys: usize) -> ! {
@ -39,6 +34,22 @@ extern "C" fn el1_bsp_lower_entry(dtb_phys: usize) -> ! {
enter_higher_half(sp as usize, elr, dtb_phys);
}
unsafe extern "C" fn el1_ap_lower_entry(sp: usize) -> ! {
ArchitectureImpl::set_interrupt_mask(true);
// Unmask FP operations
CPACR_EL1.modify(CPACR_EL1::FPEN::TrapNothing);
unsafe {
ARCHITECTURE.init_mmu(false);
}
let sp = sp.virtualize();
let elr = absolute_address!(__aarch64_ap_upper_entry);
enter_higher_half(sp, elr, 0);
}
fn enter_higher_half(sp: usize, elr: usize, arg: usize) -> ! {
unsafe {
asm!(r#"
@ -49,29 +60,12 @@ fn enter_higher_half(sp: usize, elr: usize, arg: usize) -> ! {
}
}
pub(super) extern "C" fn __aarch64_ap_lower_entry(_sp: usize) -> ! {
loop {
core::hint::spin_loop();
}
// __aarch64_common_lower_entry();
// unsafe {
// ARCHITECTURE.init_mmu(false);
// }
// let sp = unsafe { sp.virtualize() };
// let elr = absolute_address!(__aarch64_ap_upper_entry);
// enter_higher_half(sp, elr, 0);
}
extern "C" fn __aarch64_bsp_upper_entry(dtb_phys: usize) -> ! {
kernel_main(dtb_phys);
}
extern "C" fn __aarch64_ap_upper_entry(_x0: usize) -> ! {
unsafe {
AArch64::set_interrupt_mask(true);
}
assert!(ArchitectureImpl::interrupt_mask());
// Signal to BSP that we're up
CPU_COUNT.fetch_add(1, Ordering::SeqCst);
@ -81,7 +75,7 @@ extern "C" fn __aarch64_ap_upper_entry(_x0: usize) -> ! {
// Initialize CPU-local GIC and timer
unsafe {
PLATFORM.init(false).expect("AP platform init failed");
ARCHITECTURE.init_platform(false);
Cpu::init_local();
@ -100,6 +94,7 @@ static BSP_STACK: KernelStack = KernelStack {
global_asm!(
include_str!("entry.S"),
kernel_lower_entry = sym el1_bsp_lower_entry,
kernel_ap_lower_entry = sym el1_ap_lower_entry,
stack_bottom = sym BSP_STACK,
stack_size = const BOOT_STACK_SIZE,
kernel_virt_offset = const KERNEL_VIRT_OFFSET

@ -88,7 +88,7 @@ impl TaskContextImpl for TaskContext {
const SIGNAL_STACK_EXTRA_ALIGN: usize = 0;
fn kernel(entry: extern "C" fn(usize) -> !, arg: usize) -> Result<Self, Error> {
const KERNEL_TASK_PAGES: usize = 4;
const KERNEL_TASK_PAGES: usize = 8;
let stack_base = unsafe {
phys::alloc_pages_contiguous(KERNEL_TASK_PAGES, PageUsage::Used)?.virtualize()
};

@ -5,10 +5,14 @@ use aarch64_cpu::registers::{MPIDR_EL1, TPIDR_EL1};
use alloc::{boxed::Box, vec::Vec};
use tock_registers::interfaces::{Readable, Writeable};
use crate::{arch::CpuMessage, sync::IrqSafeSpinlock, task::sched::CpuQueue, util::OneTimeInit};
use crate::{
arch::CpuMessage, panic, sync::IrqSafeSpinlock, task::sched::CpuQueue, util::OneTimeInit,
};
use super::smp::CPU_COUNT;
// use super::smp::CPU_COUNT;
/// Per-CPU private data structure
#[repr(C, align(0x10))]
pub struct Cpu {
@ -108,4 +112,17 @@ impl Cpu {
(0..CPU_COUNT.load(Ordering::Acquire)).map(|_| IpiQueue::new()),
));
}
/// Gets an IPI message from the processor's queue and takes corresponding actions. If there is
/// none, this is treated as a spurious IPI and ignored. See [CpuMessage].
pub fn handle_ipi(&self) {
let Some(msg) = self.get_ipi() else {
warnln!("Spurious IPI in cpu{}", self.id);
return;
};
match msg {
CpuMessage::Panic => panic::panic_secondary(),
}
}
}

@ -40,10 +40,49 @@ pub trait DevTreeIndexPropExt {
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;
fn address_cells(&self) -> usize {
self.get_address_cells().unwrap_or(2)
}
/// 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;
fn size_cells(&self) -> usize {
self.get_size_cells().unwrap_or(1)
}
/// 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>;
}
/// Extension trait for [DevTreeIndexNode] to obtain typed property values
pub trait DevTreeIndexNodePropGet<T> {
/// Returns a property value of given type, if it exists
fn prop(&self, name: &str) -> Option<T>;
}
impl<'a, 'i, 'dt> DevTreeIndexNodePropGet<u32> for DevTreeIndexNode<'a, 'i, 'dt> {
fn prop(&self, name: &str) -> Option<u32> {
self.props().find_map(|prop| {
if prop.name().ok()? == name {
prop.u32(0).ok()
} else {
None
}
})
}
}
impl<'a, 'i, 'dt> DevTreeIndexNodePropGet<&'a str> for DevTreeIndexNode<'a, 'i, 'dt> {
fn prop(&self, name: &str) -> Option<&'a str> {
self.props().find_map(|prop| {
if prop.name().ok()? == name {
prop.str().ok()
} else {
None
}
})
}
}
/// Iterator for physical memory regions present in the device tree
@ -98,21 +137,24 @@ impl<'a> DeviceTree<'a> {
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()
}
}
impl<'a, 'i, 'dt> DevTreeIndexNodeExt for DevTreeIndexNode<'a, 'i, 'dt> {
fn address_cells(&self) -> usize {
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)
.unwrap_or(2)
}
fn size_cells(&self) -> 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)
.unwrap_or(1)
}
}

@ -10,14 +10,14 @@ use abi::{
use tock_registers::interfaces::{Readable, Writeable};
use crate::{
arch::{aarch64::cpu::Cpu, CpuMessage, PLATFORM},
arch::{aarch64::cpu::Cpu, Architecture},
debug::LogLevel,
device::interrupt::IrqContext,
panic::panic_secondary,
syscall::raw_syscall_handler,
task::{context::TaskFrame, process::Process},
};
use super::ARCHITECTURE;
/// Struct for register values saved when taking an exception
#[repr(C)]
pub struct ExceptionFrame {
@ -202,8 +202,19 @@ extern "C" fn __aa64_el0_serror_handler() {
// EL1
#[no_mangle]
extern "C" fn __aa64_el1_sync_handler(_frame: *mut ExceptionFrame) {
todo!();
extern "C" fn __aa64_el1_sync_handler(frame: *mut ExceptionFrame) {
let frame = unsafe { &*frame };
let esr_el1 = ESR_EL1.get();
let ec = (esr_el1 >> 26) & 0x3F;
let iss = esr_el1 & 0x1FFFFFF;
unsafe {
crate::sync::hack_locks();
}
dump_irrecoverable_exception(frame, ec, iss);
panic!("Irrecoverable exception in kernel mode");
}
#[no_mangle]
@ -270,10 +281,9 @@ fn el0_sync_inner(frame: &mut ExceptionFrame) {
}
fn irq_common() {
unsafe {
let ic = IrqContext::new();
PLATFORM.gic.handle_pending_irqs(&ic);
}
ARCHITECTURE
.external_interrupt_controller()
.handle_pending_irqs();
}
unsafe fn handle_signal_exit(frame: &mut ExceptionFrame) {
@ -288,15 +298,4 @@ unsafe fn handle_signal_exit(frame: &mut ExceptionFrame) {
frame.restore(&saved_data.frame);
}
pub(super) fn ipi_handler(msg: Option<CpuMessage>) {
if let Some(msg) = msg {
match msg {
CpuMessage::Panic => panic_secondary(),
}
} else {
warnln!("Spurious IPI received by cpu{}", Cpu::local_id());
todo!();
}
}
global_asm!(include_str!("vectors.S"));

@ -5,7 +5,7 @@ use tock_registers::{
registers::ReadWrite,
};
use crate::{device::interrupt::IrqContext, mem::device::DeviceMemoryIo};
use crate::mem::device::DeviceMemoryIo;
register_bitfields! {
u32,
@ -50,11 +50,11 @@ impl Gicc {
self.regs.PMR.write(PMR::Priority.val(0xFF));
}
pub fn pending_irq_number<'irq>(&'irq self, _ic: &IrqContext<'irq>) -> usize {
pub fn pending_irq_number(&self) -> usize {
self.regs.IAR.read(IAR::InterruptID) as usize
}
pub fn clear_irq<'irq>(&'irq self, irq: usize, _ic: &IrqContext<'irq>) {
pub fn clear_irq(&self, irq: usize) {
self.regs.EOIR.write(EOIR::EOINTID.val(irq as u32));
}
}

@ -1,4 +1,5 @@
//! ARM GICv2 Distributor registers
use device_api::interrupt::IpiDeliveryTarget;
use spinning_top::Spinlock;
use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
@ -6,9 +7,7 @@ use tock_registers::{
registers::{ReadOnly, ReadWrite, WriteOnly},
};
use crate::{device::interrupt::IpiDeliveryTarget, mem::device::DeviceMemoryIo};
use super::IrqNumber;
use crate::mem::device::DeviceMemoryIo;
register_bitfields! {
u32,
@ -100,11 +99,12 @@ impl Gicd {
pub unsafe fn set_sgir(&self, target: IpiDeliveryTarget, interrupt_id: u64) {
assert_eq!(interrupt_id & !0xF, 0);
let value = match target {
IpiDeliveryTarget::AllExceptLocal => SGIR::TargetListFilter::AllExceptLocal,
IpiDeliveryTarget::Specified(_mask) => {
IpiDeliveryTarget::OtherCpus => SGIR::TargetListFilter::AllExceptLocal,
IpiDeliveryTarget::Specific(_mask) => {
// TODO: need to handle self-ipi case, releasing the lock somehow
todo!();
}
IpiDeliveryTarget::ThisCpu => todo!(),
} + SGIR::INTID.val(interrupt_id as u32);
self.shared_regs.lock().SGIR.write(value);
@ -114,7 +114,7 @@ impl Gicd {
self.banked_regs.ITARGETSR[0].read(ITARGETSR::Offset0)
}
fn enable_irq_inner(&self, irq: usize) {
pub fn enable_irq(&self, irq: usize) {
let reg = irq >> 5;
let bit = 1u32 << (irq & 0x1F);
@ -135,12 +135,6 @@ impl Gicd {
}
}
pub fn enable_irq(&self, irq: IrqNumber) {
let irq = irq.get();
self.enable_irq_inner(irq);
}
pub unsafe fn init(&self) {
let mask = self.local_gic_target_mask();
let regs = self.shared_regs.lock();

@ -1,20 +1,32 @@
//! ARM Generic Interrupt Controller v2 driver
use core::sync::atomic::Ordering;
use aarch64_cpu::asm::barrier;
use abi::error::Error;
use spinning_top::Spinlock;
use alloc::boxed::Box;
use device_api::{
interrupt::{
ExternalInterruptController, FixedInterruptTable, InterruptHandler, InterruptTable,
IpiDeliveryTarget, IrqNumber, IrqOptions, LocalInterruptController,
},
Device,
};
use crate::{
device::{
interrupt::{ExternalInterruptController, InterruptSource, IrqContext},
Device, InitializableDevice,
arch::{
aarch64::devtree::{self, DevTreeIndexPropExt},
Architecture, CpuMessage,
},
device_tree_driver,
mem::device::{DeviceMemory, DeviceMemoryIo},
sync::IrqSafeSpinlock,
util::OneTimeInit,
};
use self::{gicc::Gicc, gicd::Gicd};
use super::{cpu::Cpu, exception::ipi_handler};
use super::{cpu::Cpu, smp::CPU_COUNT, ARCHITECTURE};
const MAX_IRQ: usize = 300;
const IPI_VECTOR: u64 = 1;
@ -22,49 +34,21 @@ const IPI_VECTOR: u64 = 1;
pub mod gicc;
pub mod gicd;
/// Wrapper type for ARM interrupt vector
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct IrqNumber(usize);
/// ARM Generic Interrupt Controller v2
pub struct Gic {
gicc: OneTimeInit<Gicc>,
gicd: OneTimeInit<Gicd>,
gicd_base: usize,
gicc_base: usize,
irq_table: Spinlock<[Option<&'static (dyn InterruptSource + Sync)>; MAX_IRQ]>,
}
impl IrqNumber {
/// Returns the underlying vector number
#[inline(always)]
pub const fn get(self) -> usize {
self.0
}
/// Wraps the interrupt vector value in the [IrqNumber] type.
///
/// # Panics
///
/// Will panic if `v` is not a valid interrupt number.
#[inline(always)]
pub const fn new(v: usize) -> Self {
assert!(v < MAX_IRQ);
Self(v)
}
table: IrqSafeSpinlock<FixedInterruptTable<MAX_IRQ>>,
}
impl Device for Gic {
fn name(&self) -> &'static str {
fn display_name(&self) -> &'static str {
"ARM Generic Interrupt Controller v2"
}
}
impl InitializableDevice for Gic {
type Options = ();
unsafe fn init(&self, _opts: ()) -> Result<(), Error> {
unsafe fn init(&'static self) -> Result<(), Error> {
let gicd_mmio = DeviceMemory::map("GICv2 Distributor registers", self.gicd_base, 0x1000)?;
let gicd_mmio_shared = DeviceMemoryIo::new(gicd_mmio.clone());
let gicd_mmio_banked = DeviceMemoryIo::new(gicd_mmio);
@ -79,66 +63,104 @@ impl InitializableDevice for Gic {
self.gicd.init(gicd);
self.gicc.init(gicc);
ARCHITECTURE.register_external_interrupt_controller(self)?;
ARCHITECTURE.register_local_interrupt_controller(self)?;
Ok(())
}
}
impl ExternalInterruptController for Gic {
type IrqNumber = IrqNumber;
fn enable_irq(&self, irq: Self::IrqNumber) -> Result<(), Error> {
self.gicd.get().enable_irq(irq);
Ok(())
}
fn register_handler(
fn register_irq(
&self,
irq: Self::IrqNumber,
handler: &'static (dyn InterruptSource + Sync),
irq: IrqNumber,
_options: IrqOptions,
handler: &'static dyn InterruptHandler,
) -> Result<(), Error> {
let mut table = self.irq_table.lock();
let irq = irq.get();
if table[irq].is_some() {
return Err(Error::AlreadyExists);
}
let mut table = self.table.lock();
debugln!("Bound irq{} to {:?}", irq, Device::name(handler));
table[irq] = Some(handler);
let index = match irq {
IrqNumber::Shared(i) => i + 32,
IrqNumber::Private(i) => i + 16,
} as usize;
debugln!("Bound irq{} to {:?}", index, handler.display_name());
table.insert(index, handler)?;
Ok(())
}
fn configure_irq(
&self,
_irq: Self::IrqNumber,
_active_low: bool,
_level_triggered: bool,
) -> Result<(), Error> {
todo!()
fn enable_irq(&self, irq: IrqNumber) -> Result<(), Error> {
let gicd = self.gicd.get();
let index = match irq {
IrqNumber::Shared(i) => i + 32,
IrqNumber::Private(i) => i + 16,
} as usize;
gicd.enable_irq(index);
Ok(())
}
// unsafe fn send_ipi(&self, target: IpiDeliveryTarget, msg: CpuMessage) -> Result<(), Error> {
// // TODO message queue insertion should be moved
// match target {
// IpiDeliveryTarget::AllExceptLocal => {
// let local = Cpu::local_id();
// for i in 0..CPU_COUNT.load(Ordering::Acquire) {
// if i != local as usize {
// Cpu::push_ipi_queue(i as u32, msg);
// }
// }
// }
// IpiDeliveryTarget::Specified(_) => todo!(),
// }
fn handle_pending_irqs(&self) {
let gicc = self.gicc.get();
let irq_number = gicc.pending_irq_number();
if irq_number >= MAX_IRQ {
return;
}
// // Issue a memory barrier
// barrier::dsb(barrier::ISH);
// barrier::isb(barrier::SY);
if irq_number == IPI_VECTOR as usize {
gicc.clear_irq(irq_number);
Cpu::local().handle_ipi();
return;
}
// self.gicd.get().set_sgir(target, IPI_VECTOR);
gicc.clear_irq(irq_number);
// Ok(())
// }
{
let table = self.table.lock();
match table.handler(irq_number) {
Some(handler) => {
drop(table);
handler.handle_irq();
}
None => warnln!("No handler for irq{}", irq_number),
}
}
}
}
impl LocalInterruptController for Gic {
type IpiMessage = CpuMessage;
fn send_ipi(&self, target: IpiDeliveryTarget, msg: Self::IpiMessage) -> Result<(), Error> {
// TODO message queue insertion should be moved
match target {
IpiDeliveryTarget::OtherCpus => {
let local = Cpu::local_id();
for i in 0..CPU_COUNT.load(Ordering::Acquire) {
if i != local as usize {
Cpu::push_ipi_queue(i as u32, msg);
}
}
}
IpiDeliveryTarget::Specific(_) => todo!(),
IpiDeliveryTarget::ThisCpu => todo!(),
}
// Issue a memory barrier
barrier::dsb(barrier::ISH);
barrier::isb(barrier::SY);
unsafe {
self.gicd.get().set_sgir(target, IPI_VECTOR);
}
Ok(())
}
unsafe fn init_ap(&self) -> Result<(), Error> {
self.gicc.get().init();
Ok(())
}
}
impl Gic {
@ -153,46 +175,19 @@ impl Gic {
gicd: OneTimeInit::new(),
gicd_base,
gicc_base,
irq_table: Spinlock::new([None; MAX_IRQ]),
}
}
/// Initializes GICv2 for an application processor.
///
/// # Safety
///
/// Must not be called more than once per each AP. Must not be called from BSP.
pub unsafe fn init_smp_ap(&self) -> Result<(), Error> {
self.gicc.get().init();
Ok(())
}
/// Handles a single pending IRQ (if there're many, the CPU will just get here again)
pub fn handle_pending_irqs<'irq>(&'irq self, ic: &IrqContext<'irq>) {
let gicc = self.gicc.get();
let irq_number = gicc.pending_irq_number(ic);
if irq_number >= MAX_IRQ {
return;
}
gicc.clear_irq(irq_number, ic);
if irq_number as u64 == IPI_VECTOR {
// IPI received
let msg = Cpu::local().get_ipi();
ipi_handler(msg);
return;
}
{
let table = self.irq_table.lock();
match table[irq_number] {
None => panic!("No IRQ handler registered for irq{}", irq_number),
Some(handler) => {
drop(table);
handler.handle_irq().expect("IRQ handler failed");
}
}
table: IrqSafeSpinlock::new(FixedInterruptTable::new()),
}
}
}
device_tree_driver! {
compatible: ["arm,cortex-a15-gic", "arm,gic-400"],
probe(dt) => {
let reg = devtree::find_prop(&dt.node, "reg")?;
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)?;
Some(Box::new(unsafe { Gic::new(gicc_base as usize, gicd_base as usize) }))
}
}

@ -2,18 +2,27 @@
use core::sync::atomic::Ordering;
use aarch64_cpu::registers::{DAIF, ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1, TTBR1_EL1};
use aarch64_cpu::registers::{
CNTP_CTL_EL0, CNTP_TVAL_EL0, DAIF, ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1, TTBR1_EL1,
};
use abi::error::Error;
use cfg_if::cfg_if;
use device_api::{
interrupt::{
ExternalInterruptController, IpiDeliveryTarget, IrqNumber, LocalInterruptController,
},
timer::MonotonicTimestampProviderDevice,
};
use fdt_rs::prelude::PropReader;
use git_version::git_version;
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
use crate::{
arch::{
aarch64::{cpu::Cpu, devtree::FdtMemoryRegionIter, smp::CPU_COUNT},
aarch64::{cpu::Cpu, devtree::FdtMemoryRegionIter},
Architecture,
},
debug,
device::platform::Platform,
debug::{self},
device::{self, power::arm_psci::Psci, DevTreeNodeInfo},
fs::{devfs, Initrd, INITRD_DATA},
mem::{
heap,
@ -25,23 +34,13 @@ use crate::{
};
use self::{
boot::CPU_INIT_FENCE,
devtree::{DevTreeIndexPropExt, DeviceTree},
smp::CPU_COUNT,
table::{init_fixed_tables, KERNEL_TABLES},
};
pub mod plat_qemu;
cfg_if! {
if #[cfg(feature = "aarch64_qemu")] {
pub use plat_qemu::{PlatformImpl, PLATFORM};
} else if #[cfg(feature = "aarch64_orange_pi3")] {
pub mod plat_orange_pi3;
pub use plat_orange_pi3::{PlatformImpl, PLATFORM};
} else {
compile_error!("No platform selected for AArch64");
}
}
use super::CpuMessage;
pub mod boot;
pub mod context;
@ -64,11 +63,24 @@ struct KernelStack {
/// AArch64 platform interface
pub struct AArch64 {
dt: OneTimeInit<DeviceTree<'static>>,
ext_intc: OneTimeInit<&'static dyn ExternalInterruptController>,
local_intc: OneTimeInit<&'static dyn LocalInterruptController<IpiMessage = CpuMessage>>,
mtimer: OneTimeInit<&'static dyn MonotonicTimestampProviderDevice>,
// ARM-only devices
/// ARM PSCI instance on this system (there may not be one)
pub psci: OneTimeInit<&'static Psci>,
}
/// Global platform handle
pub static ARCHITECTURE: AArch64 = AArch64 {
dt: OneTimeInit::new(),
ext_intc: OneTimeInit::new(),
local_intc: OneTimeInit::new(),
mtimer: OneTimeInit::new(),
// ARM-only devices
psci: OneTimeInit::new(),
};
impl Architecture for AArch64 {
@ -123,6 +135,56 @@ impl Architecture for AArch64 {
fn cpu_count() -> usize {
CPU_COUNT.load(Ordering::Acquire)
}
fn register_external_interrupt_controller(
&self,
intc: &'static dyn ExternalInterruptController,
) -> Result<(), Error> {
self.ext_intc.init(intc);
Ok(())
}
fn register_local_interrupt_controller(
&self,
intc: &'static dyn LocalInterruptController<IpiMessage = super::CpuMessage>,
) -> Result<(), Error> {
self.local_intc.init(intc);
Ok(())
}
fn register_monotonic_timer(
&self,
timer: &'static dyn MonotonicTimestampProviderDevice,
) -> Result<(), Error> {
self.mtimer.init(timer);
Ok(())
}
fn external_interrupt_controller(&self) -> &'static dyn ExternalInterruptController {
*self.ext_intc.get()
}
fn local_interrupt_controller(
&self,
) -> &'static dyn LocalInterruptController<IpiMessage = super::CpuMessage> {
*self.local_intc.get()
}
fn monotonic_timer(&self) -> &'static dyn MonotonicTimestampProviderDevice {
*self.mtimer.get()
}
unsafe fn send_ipi(&self, target: IpiDeliveryTarget, msg: CpuMessage) -> Result<(), Error> {
if let Some(local_intc) = self.local_intc.try_get() {
local_intc.send_ipi(target, msg)
} else {
Ok(())
}
}
unsafe fn reset(&self) -> ! {
todo!()
}
}
impl AArch64 {
@ -170,6 +232,109 @@ impl AArch64 {
phys::init_from_iter(regions)
}
fn chosen_stdout_path<'a>(dt: &'a DeviceTree) -> Option<&'a str> {
let chosen = dt.node_by_path("/chosen")?;
let prop = devtree::find_prop(&chosen, "stdout-path")?;
prop.str().ok()
}
fn init_platform(&self, bsp: bool) {
if bsp {
let dt = self.device_tree();
let address_cells = dt.address_cells();
let size_cells = dt.size_cells();
let chosen_stdout_path = Self::chosen_stdout_path(dt);
let chosen_stdout = chosen_stdout_path.and_then(|path| dt.node_by_path(path));
// Probe and initialize the /chosen.stdout-path device first
if let Some(node) = chosen_stdout.clone() {
let probe = DevTreeNodeInfo {
address_cells,
size_cells,
node,
};
if let Some((device, _)) = device::probe_dt_node(&probe) {
unsafe {
device.init().unwrap();
}
}
};
debug::reset();
// Print some stuff now that the output is initialized
infoln!(
"Yggdrasil v{} ({})",
env!("CARGO_PKG_VERSION"),
git_version!()
);
infoln!("Initializing aarch64 platform");
// Probe and initialize the rest of devices
let nodes = dt.root().children();
if let Err(error) = device::enumerate_dt(
address_cells,
size_cells,
nodes,
|_, probe| {
// Ignore /chosen/stdout-path node
if let Some(ref chosen_stdout) = chosen_stdout && chosen_stdout.name() == probe.node.name() {
return Ok(());
}
if let Some((device, _)) = device::probe_dt_node(&probe) {
unsafe {
device.init()?;
}
}
Ok(())
},
) {
warnln!(
"{} errors encountered when initializing platform devices",
error
);
}
// Initialize IRQs for the devices
device::manager_lock().devices().for_each(|dev| unsafe {
if let Err(error) = dev.init_irq() {
errorln!(
"Could not init interrupts for {:?}: {:?}",
dev.display_name(),
error
);
}
});
// Print the device list
infoln!("Enumerated devices:");
device::manager_lock().devices().for_each(|dev| {
infoln!("* {:?}", dev.display_name());
});
} else {
// BSP already initialized everything needed
// Setup timer and local interrupt controller
let intc = self.local_intc.get();
unsafe {
intc.init_ap().unwrap();
}
// TODO device-tree initialization for this
CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::CLEAR);
CNTP_TVAL_EL0.set(10000000);
self.ext_intc
.get()
.enable_irq(IrqNumber::Private(14))
.unwrap();
}
}
}
fn setup_initrd() {
@ -228,21 +393,15 @@ pub fn kernel_main(dtb_phys: usize) -> ! {
TTBR0_EL1.set(0);
unsafe {
AArch64::set_interrupt_mask(true);
PLATFORM.init_debug();
ARCHITECTURE.init_device_tree(dtb_phys);
}
debug::reset();
AArch64::set_interrupt_mask(true);
exception::init_exceptions();
// Setup initrd
setup_initrd();
debugln!("Initializing {} platform", PLATFORM.name());
unsafe {
ARCHITECTURE
.init_physical_memory(dtb_phys)
.expect("Failed to initialize the physical memory manager");
@ -252,27 +411,32 @@ pub fn kernel_main(dtb_phys: usize) -> ! {
.expect("Could not allocate a block for heap");
heap::init_heap(heap_base.virtualize(), 16 * 0x1000);
devfs::init();
// Enumerate the device tree
ARCHITECTURE.init_platform(true);
debugln!("Heap: {:#x?}", heap::heap_range());
Cpu::init_local();
devfs::init();
PLATFORM.init(true).unwrap();
let dt = ARCHITECTURE.dt.get();
if let Err(e) = smp::start_ap_cores(dt) {
errorln!(
"Could not initialize AP CPUs: {:?}. Will continue with one CPU.",
e
);
}
// let dt = ARCHITECTURE.dt.get();
// if let Err(e) = smp::start_ap_cores(dt) {
// errorln!(
// "Could not initialize AP CPUs: {:?}. Will continue with one CPU.",
// e
// );
// }
Cpu::init_ipi_queues();
// Cpu::init_ipi_queues();
// CPU_INIT_FENCE.signal();
// CPU_INIT_FENCE.wait_all(CPU_COUNT.load(Ordering::Acquire));
CPU_INIT_FENCE.signal();
CPU_INIT_FENCE.wait_all(CPU_COUNT.load(Ordering::Acquire));
task::init().expect("Failed to initialize the scheduler");
// Initialize and enter the scheduler
infoln!("All cpus ready");
task::enter();
}
}

@ -35,29 +35,31 @@ impl Platform for PlatformImpl {
const KERNEL_PHYS_BASE: usize = 0x40080000;
unsafe fn init(&'static self, is_bsp: bool) -> Result<(), Error> {
if is_bsp {
self.gic.init(())?;
loop {}
// if is_bsp {
// self.gic.init(())?;
self.pl011.init_irq()?;
devfs::add_char_device(&self.pl011, CharDeviceType::TtySerial)?;
// self.pl011.init_irq()?;
// devfs::add_char_device(&self.pl011, CharDeviceType::TtySerial)?;
self.local_timer.init(())?;
self.local_timer.init_irq()?;
} else {
self.gic.init_smp_ap()?;
// self.local_timer.init(())?;
// self.local_timer.init_irq()?;
// } else {
// self.gic.init_smp_ap()?;
// TODO somehow merge this with the rest of the code
CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::CLEAR);
CNTP_TVAL_EL0.set(10000000);
self.gic.enable_irq(IrqNumber::new(30))?;
}
// // TODO somehow merge this with the rest of the code
// CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::CLEAR);
// CNTP_TVAL_EL0.set(10000000);
// self.gic.enable_irq(IrqNumber::new(30))?;
// }
Ok(())
// Ok(())
}
unsafe fn init_debug(&'static self) {
self.pl011.init(()).ok();
debug::add_sink(&self.pl011, LogLevel::Debug);
loop {}
// self.pl011.init(()).ok();
// debug::add_sink(&self.pl011, LogLevel::Debug);
}
fn interrupt_controller(

@ -1,60 +1,79 @@
//! Simultaneous multiprocessing support for aarch64
use core::{
arch::asm,
sync::atomic::{AtomicUsize, Ordering},
};
use core::sync::atomic::{AtomicUsize, Ordering};
use abi::error::Error;
use device_api::CpuBringupDevice;
use fdt_rs::prelude::PropReader;
use crate::{
absolute_address,
arch::aarch64::boot::__aarch64_ap_lower_entry,
arch::ARCHITECTURE,
mem::{
phys::{self, PageUsage},
ConvertAddress, KERNEL_VIRT_OFFSET,
ConvertAddress,
},
};
use super::devtree::{self, DeviceTree};
use super::devtree::{self, DevTreeIndexNodePropGet, DeviceTree};
/// ARM Power State Coordination Interface
pub struct Psci {}
#[derive(Debug)]
enum CpuEnableMethod {
Psci,
// Not currently supported
#[allow(dead_code)]
SpinTable {
release_addr: usize,
},
}
struct CpuInfo<'a> {
id: u32,
compatible: &'a str,
enable_method: CpuEnableMethod,
}
fn enumerate_cpus<'a>(dt: &'a DeviceTree) -> impl Iterator<Item = CpuInfo<'a>> {
let cpus = dt.node_by_path("/cpus").unwrap();
cpus.children().filter_map(|cpu_node| {
let compatible = devtree::find_prop(&cpu_node, "compatible").and_then(|p| p.str().ok())?;
let id = cpu_node.prop("reg")?; // devtree::find_prop(&cpu_node, "reg").and_then(|p| p.u32(0).ok())?;
let enable_method_str: &str = cpu_node.prop("enable-method")?;
let enable_method = match enable_method_str {
"psci" => CpuEnableMethod::Psci,
_ => todo!(),
};
Some(CpuInfo {
id,
compatible,
enable_method,
})
})
}
impl CpuEnableMethod {
unsafe fn start_cpu(&self, id: usize, ip: usize, sp: usize) -> Result<(), Error> {
match self {
Self::Psci => {
let psci = ARCHITECTURE.psci.try_get().ok_or_else(|| {
warnln!(
"cpu{} has to be enabled through PSCI, but no PSCI found",
id
);
Error::InvalidArgument
})?;
psci.start_cpu(id, ip, sp)
}
_ => todo!(),
}
}
}
/// Number of online CPUs, initially set to 1 (BSP processor is up)
pub static CPU_COUNT: AtomicUsize = AtomicUsize::new(1);
impl Psci {
/// Function ID for CPU startup request
const CPU_ON: u32 = 0xC4000003;
/// Constructs an interface instance for PSCI
pub const fn new() -> Self {
Self {}
}
#[inline]
unsafe fn call(&self, mut x0: u64, x1: u64, x2: u64, x3: u64) -> u64 {
asm!("hvc #0", inout("x0") x0, in("x1") x1, in("x2") x2, in("x3") x3);
x0
}
/// Enables a single processor through a hvc/svc call.
///
/// # Safety
///
/// Calling this outside of initialization sequence or more than once may lead to unexpected
/// behavior.
pub unsafe fn cpu_on(&self, target_cpu: usize, entry_point_address: usize, context_id: usize) {
self.call(
Self::CPU_ON as _,
target_cpu as _,
entry_point_address as _,
context_id as _,
);
}
}
/// Starts application processors using the method specified in the device tree.
///
/// TODO: currently does not handle systems where APs are already started before entry.
@ -64,68 +83,42 @@ impl Psci {
/// The caller must ensure the physical memory manager was initialized, virtual memory tables are
/// set up and the function has not been called before.
pub unsafe fn start_ap_cores(dt: &DeviceTree) -> Result<(), Error> {
let cpus = dt.node_by_path("/cpus").unwrap();
let psci = Psci::new();
for cpu in cpus.children() {
let Some(compatible) = devtree::find_prop(&cpu, "compatible") else {
continue;
};
let Ok(compatible) = compatible.str() else {
continue;
};
if !compatible.starts_with("arm,cortex-a") {
continue;
}
let reg = devtree::find_prop(&cpu, "reg").unwrap().u32(0).unwrap();
if reg == 0 {
continue;
extern "C" {
fn __aarch64_ap_entry();
}
for cpu in enumerate_cpus(dt).filter(|cpu| cpu.id != 0) {
debugln!(
"Will start {}, compatible={:?}, reg={}",
cpu.name().unwrap(),
compatible,
reg
"cpu{}: enable-method={:?}, compatible={:?}",
cpu.id,
cpu.enable_method,
cpu.compatible
);
const AP_STACK_PAGES: usize = 4;
let stack_pages = phys::alloc_pages_contiguous(AP_STACK_PAGES, PageUsage::Used)?;
debugln!(
"{} stack: {:#x}..{:#x}",
cpu.name().unwrap(),
"cpu{} stack: {:#x}..{:#x}",
cpu.id,
stack_pages,
stack_pages + AP_STACK_PAGES * 0x1000
);
// Wait for the CPU to come up
let old_count = CPU_COUNT.load(Ordering::Acquire);
psci.cpu_on(
reg as usize,
absolute_address!(__aarch64_ap_entry).physicalize(),
stack_pages + AP_STACK_PAGES * 0x1000,
);
let ip = absolute_address!(__aarch64_ap_entry).physicalize();
let sp = stack_pages + AP_STACK_PAGES * 0x1000;
if let Err(error) = cpu.enable_method.start_cpu(cpu.id as usize, ip, sp) {
errorln!("Couldn't start cpu{} up: {:?}", cpu.id, error);
continue;
}
while CPU_COUNT.load(Ordering::Acquire) == old_count {
aarch64_cpu::asm::wfe();
}
debugln!("{} is up", cpu.name().unwrap());
debugln!("cpu{} is up", cpu.id);
}
Ok(())
}
#[naked]
unsafe extern "C" fn __aarch64_ap_entry() -> ! {
asm!(
r#"
mov sp, x0
bl {entry} - {kernel_virt_offset}
"#,
entry = sym __aarch64_ap_lower_entry,
kernel_virt_offset = const KERNEL_VIRT_OFFSET,
options(noreturn)
);
}

@ -362,7 +362,10 @@ impl VirtualMemoryManager for AddressSpace {
for page in (addr..addr + len).step_by(0x1000) {
let Some(phys) = self.translate(page) else {
todo!();
todo!(
"Tried to deallocate address not present in the table: {:#x}",
addr
);
};
self.write_entry(page, PageEntry::INVALID, true)?;

@ -4,14 +4,17 @@ use core::time::Duration;
use aarch64_cpu::registers::{CNTFRQ_EL0, CNTPCT_EL0, CNTP_CTL_EL0, CNTP_TVAL_EL0};
use abi::error::Error;
use alloc::boxed::Box;
use device_api::{
interrupt::{InterruptHandler, IrqNumber},
timer::MonotonicTimestampProviderDevice,
Device,
};
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
use crate::{
arch::{aarch64::gic::IrqNumber, PLATFORM},
device::{
interrupt::InterruptSource, platform::Platform, timer::TimestampSource, Device,
InitializableDevice,
},
arch::{Architecture, ARCHITECTURE},
device_tree_driver,
proc::wait,
task::tasklet,
};
@ -26,23 +29,24 @@ pub struct ArmTimer {
/// ARM timer tick interval (in some time units?)
pub const TICK_INTERVAL: u64 = 1000000;
impl Device for ArmTimer {
fn name(&self) -> &'static str {
"ARM Generic Timer"
impl InterruptHandler for ArmTimer {
fn handle_irq(&self) -> bool {
CNTP_TVAL_EL0.set(TICK_INTERVAL);
let now = self.monotonic_timestamp().unwrap();
wait::tick(now);
tasklet::tick(now);
unsafe {
Cpu::local().queue().yield_cpu();
}
true
}
}
impl InitializableDevice for ArmTimer {
type Options = ();
unsafe fn init(&self, _opts: ()) -> Result<(), Error> {
CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET);
Ok(())
}
}
impl TimestampSource for ArmTimer {
fn timestamp(&self) -> Result<Duration, Error> {
impl MonotonicTimestampProviderDevice for ArmTimer {
fn monotonic_timestamp(&self) -> Result<Duration, Error> {
let count = CNTPCT_EL0.get() * 1_000_000;
let freq = CNTFRQ_EL0.get();
@ -50,33 +54,61 @@ impl TimestampSource for ArmTimer {
}
}
impl InterruptSource for ArmTimer {
fn handle_irq(&self) -> Result<bool, Error> {
CNTP_TVAL_EL0.set(TICK_INTERVAL);
let t = self.timestamp()?;
wait::tick(t);
tasklet::tick(t);
unsafe {
Cpu::local().queue().yield_cpu();
impl Device for ArmTimer {
fn display_name(&self) -> &'static str {
"ARM Generic Timer"
}
Ok(true)
unsafe fn init(&'static self) -> Result<(), Error> {
CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET);
ARCHITECTURE.register_monotonic_timer(self)?;
Ok(())
}
unsafe fn init_irq(&'static self) -> Result<(), Error> {
let intc = PLATFORM.interrupt_controller();
let intc = ARCHITECTURE.external_interrupt_controller();
intc.register_irq(self.irq, Default::default(), self)?;
intc.register_handler(self.irq, self)?;
CNTP_CTL_EL0.modify(CNTP_CTL_EL0::IMASK::CLEAR);
CNTP_TVAL_EL0.set(TICK_INTERVAL);
intc.enable_irq(self.irq)?;
Ok(())
}
}
// impl TimestampSource for ArmTimer {
// fn timestamp(&self) -> Result<Duration, Error> {
// }
// }
// impl InterruptSource for ArmTimer {
// fn handle_irq(&self) -> Result<bool, Error> {
// CNTP_TVAL_EL0.set(TICK_INTERVAL);
// let t = self.timestamp()?;
//
// wait::tick(t);
// tasklet::tick(t);
//
// unsafe {
// Cpu::local().queue().yield_cpu();
// }
//
// Ok(true)
// }
//
// unsafe fn init_irq(&'static self) -> Result<(), Error> {
// let intc = PLATFORM.interrupt_controller();
//
// intc.register_handler(self.irq, self)?;
// intc.enable_irq(self.irq)?;
//
// Ok(())
// }
// }
impl ArmTimer {
/// Constructs an instance of ARM generic timer.
///
@ -87,3 +119,11 @@ impl ArmTimer {
Self { irq }
}
}
device_tree_driver! {
compatible: ["arm,armv8-timer"],
probe(_dt) => {
// TODO actually get info from the dt
Some(Box::new(unsafe { ArmTimer::new(IrqNumber::Private(14)) }))
}
}

@ -1,7 +1,6 @@
//! Provides architecture/platform-specific implementation details
use abi::error::Error;
use cfg_if::cfg_if;
/// Returns an absolute address to the given symbol
#[macro_export]
@ -21,23 +20,29 @@ macro_rules! absolute_address {
}
pub mod aarch64;
cfg_if! {
if #[cfg(target_arch = "aarch64")] {
pub use aarch64::{AArch64 as ArchitectureImpl, ARCHITECTURE, PlatformImpl, PLATFORM};
} else if #[cfg(target_arch = "x86_64")] {
pub mod x86_64;
pub use x86_64::{
X86_64 as ArchitectureImpl,
X86_64 as PlatformImpl,
ARCHITECTURE,
ARCHITECTURE as PLATFORM
};
} else {
compile_error!("Architecture is not supported");
}
}
pub use aarch64::{AArch64 as ArchitectureImpl, ARCHITECTURE};
use device_api::{
interrupt::{ExternalInterruptController, IpiDeliveryTarget, LocalInterruptController},
timer::MonotonicTimestampProviderDevice,
};
// cfg_if! {
// if #[cfg(target_arch = "aarch64")] {
//
// pub use aarch64::{AArch64 as ArchitectureImpl, ARCHITECTURE, PlatformImpl, PLATFORM};
// } else if #[cfg(target_arch = "x86_64")] {
// pub mod x86_64;
//
// pub use x86_64::{
// X86_64 as ArchitectureImpl,
// X86_64 as PlatformImpl,
// ARCHITECTURE,
// ARCHITECTURE as PLATFORM
// };
// } else {
// compile_error!("Architecture is not supported");
// }
// }
/// Describes messages sent from some CPU to others
#[derive(Clone, Copy, PartialEq, Debug)]
@ -82,4 +87,54 @@ pub trait Architecture {
/// Returns the count of present CPUs, including the BSP
fn cpu_count() -> usize;
/// Adds an external interrupt controller to the system
fn register_external_interrupt_controller(
&self,
intc: &'static dyn ExternalInterruptController,
) -> Result<(), Error>;
/// Adds a local interrupt controller to the system
fn register_local_interrupt_controller(
&self,
intc: &'static dyn LocalInterruptController<IpiMessage = CpuMessage>,
) -> Result<(), Error>;
/// Adds a monotonic timer to the system
fn register_monotonic_timer(
&self,
timer: &'static dyn MonotonicTimestampProviderDevice,
) -> Result<(), Error>;
// TODO only supports 1 extintc per system
/// Returns the primary external interrupt controller
fn external_interrupt_controller(&self) -> &'static dyn ExternalInterruptController;
/// Returns the local interrupt controller
fn local_interrupt_controller(
&self,
) -> &'static dyn LocalInterruptController<IpiMessage = CpuMessage>;
/// Returns the monotonic timer
fn monotonic_timer(&self) -> &'static dyn MonotonicTimestampProviderDevice;
/// Sends a message to the requested set of CPUs through an interprocessor interrupt.
///
/// # Note
///
/// u64 limits the number of targetable CPUs to (only) 64. Platform-specific implementations
/// may impose narrower restrictions.
///
/// # Safety
///
/// As the call may alter the flow of execution on CPUs, this function is unsafe.
unsafe fn send_ipi(&self, target: IpiDeliveryTarget, msg: CpuMessage) -> Result<(), Error>;
/// Performs a CPU reset.
///
/// # Safety
///
/// The caller must ensure it is actually safe to reset, i.e. no critical processes will be
/// aborted and no data will be lost.
unsafe fn reset(&self) -> !;
}

@ -20,7 +20,7 @@ use crate::{
task,
};
use super::{smp::CPU_COUNT, ARCHITECTURE};
use super::ARCHITECTURE;
const BOOT_STACK_SIZE: usize = 65536;
@ -81,39 +81,40 @@ unsafe extern "C" fn __x86_64_upper_entry() -> ! {
/// Application processor entry point
pub extern "C" fn __x86_64_ap_entry() -> ! {
let cpu_id = CPU_COUNT.load(Ordering::Acquire);
loop {}
// let cpu_id = CPU_COUNT.load(Ordering::Acquire);
MSR_IA32_KERNEL_GS_BASE.set(&UNINIT_CPU as *const _ as u64);
unsafe {
core::arch::asm!("swapgs");
}
MSR_IA32_KERNEL_GS_BASE.set(&UNINIT_CPU as *const _ as u64);
unsafe {
core::arch::asm!("swapgs");
}
// MSR_IA32_KERNEL_GS_BASE.set(&UNINIT_CPU as *const _ as u64);
// unsafe {
// core::arch::asm!("swapgs");
// }
// MSR_IA32_KERNEL_GS_BASE.set(&UNINIT_CPU as *const _ as u64);
// unsafe {
// core::arch::asm!("swapgs");
// }
// Still not initialized: GDT, IDT, CPU features, syscall, kernel_gs_base
cpuid::feature_gate();
// // Still not initialized: GDT, IDT, CPU features, syscall, kernel_gs_base
// cpuid::feature_gate();
infoln!("cpu{} initializing", cpu_id);
unsafe {
ARCHITECTURE.init_mmu(false);
core::arch::asm!("wbinvd");
// infoln!("cpu{} initializing", cpu_id);
// unsafe {
// ARCHITECTURE.init_mmu(false);
// core::arch::asm!("wbinvd");
Cpu::init_local(LocalApic::new(), cpu_id as u32);
syscall::init_syscall();
exception::init_exceptions(cpu_id);
// Cpu::init_local(LocalApic::new(), cpu_id as u32);
// syscall::init_syscall();
// exception::init_exceptions(cpu_id);
ARCHITECTURE.init(false).unwrap();
}
// ARCHITECTURE.init(false).unwrap();
// }
CPU_COUNT.fetch_add(1, Ordering::Release);
// CPU_COUNT.fetch_add(1, Ordering::Release);
CPU_INIT_FENCE.wait_one();
// CPU_INIT_FENCE.wait_one();
infoln!("cpu{} initialized", cpu_id);
// infoln!("cpu{} initialized", cpu_id);
unsafe { task::enter() }
// unsafe { task::enter() }
}
global_asm!(

3
src/device/bus/mod.rs Normal file

@ -0,0 +1,3 @@
//! Bus devices
pub mod simple_bus;

@ -0,0 +1,27 @@
//! Simple "passthrough" bus device
use crate::{arch::aarch64::devtree::DevTreeIndexNodeExt, device, device_tree_driver};
device_tree_driver! {
compatible: ["simple-bus"],
probe(dt) => {
let address_cells = dt.node.address_cells();
let size_cells = dt.node.size_cells();
let nodes = dt.node.children();
// Iterate devices on the bus
device::enumerate_dt(address_cells, size_cells, nodes, |_, probe| {
if let Some((device, _)) = device::probe_dt_node(&probe) {
unsafe {
device.init()?;
}
}
Ok(())
}).ok();
// Don't yield any devices
None
}
}

@ -1,88 +0,0 @@
//! Interrupt-related interfaces
use core::marker::PhantomData;
use abi::error::Error;
use super::Device;
/// Convenience type alias for a static IRQ handler
pub type IrqHandler = &'static (dyn InterruptSource + Sync);
/// Specifies the target(s) of interprocessor interrupt delivery
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum IpiDeliveryTarget {
/// IPI will be delivered to every CPU except the local one
AllExceptLocal,
/// IPI will only be sent to CPUs specified in the mask
Specified(u64),
}
/// Interface for a device capable of emitting interrupts
pub trait InterruptSource: Device {
/// Initializes and enables IRQs for the device.
///
/// # Safety
///
/// The caller must ensure the function hasn't been called before.
unsafe fn init_irq(&'static self) -> Result<(), Error>;
/// Handles the interrupt raised by the device. Returns `true` if the interrupt was handled,
/// `false` if device itself sent no interrupt (may be possible if several interrupt handlers
/// share the same IRQ vector).
fn handle_irq(&self) -> Result<bool, Error>;
}
// TODO not yet ready for this
// /// Controls per-CPU interrupt delivery
// pub trait LocalInterruptController {
// type Vector;
// type Id;
//
// /// Allocates a vector for external IRQ
// unsafe fn allocate_irq_vector(&self) -> Result<Self::Vector, Error>;
//
// /// Returns the unique ID of this local interrupt controller
// fn id(&self) -> Self::Id;
//
// }
/// Interface for a device responsible for routing and handling IRQs from external sources
pub trait ExternalInterruptController: Device {
/// Interrupt number wrapper type
type IrqNumber;
/// Binds an interrupt number to its handler implementation
fn register_handler(
&self,
irq: Self::IrqNumber,
handler: &'static (dyn InterruptSource + Sync),
) -> Result<(), Error>;
/// Configures how an interrupt should be handled by the controller
fn configure_irq(
&self,
irq: Self::IrqNumber,
active_low: bool,
level_triggered: bool,
) -> Result<(), Error>;
/// Enables given interrupt number/vector
fn enable_irq(&self, irq: Self::IrqNumber) -> Result<(), Error>;
}
/// Token type to indicate that the code is being run from an interrupt handler
pub struct IrqContext<'irq> {
_0: PhantomData<&'irq ()>,
}
impl<'irq> IrqContext<'irq> {
/// Constructs an IRQ context token
///
/// # Safety
///
/// Only allowed to be constructed in top-level IRQ handlers
#[inline(always)]
pub const unsafe fn new() -> Self {
Self { _0: PhantomData }
}
}

@ -1,31 +1,185 @@
//! Device management and interfaces
use abi::error::Error;
use core::mem::size_of;
#[cfg(feature = "fb_console")]
pub mod display;
pub mod input;
pub mod interrupt;
pub mod platform;
use abi::error::Error;
use alloc::boxed::Box;
use device_api::{manager::DeviceManager, Device, DeviceId};
use fdt_rs::{index::DevTreeIndexNode, prelude::PropReader};
use crate::{
arch::aarch64::devtree,
sync::{IrqSafeSpinlock, IrqSafeSpinlockGuard},
};
pub mod bus;
pub mod power;
pub mod serial;
pub mod timer;
pub mod tty;
/// General device interface
pub trait Device {
/// Returns a display name for the device
fn name(&self) -> &'static str;
// pub mod display;
static DEVICE_MANAGER: IrqSafeSpinlock<DeviceManager> = IrqSafeSpinlock::new(DeviceManager::new());
/// 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)*));
}
/// Interface for device initialization
pub trait InitializableDevice {
/// Options provided when initializing the device
type Options;
/// 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),+];
/// Initializes the device.
///
/// # Safety
///
/// Unsafe: only meant to be called once for each device.
unsafe fn init(&self, opts: Self::Options) -> Result<(), Error>;
fn __probe($node: &$crate::device::DevTreeNodeInfo) ->
Option<alloc::boxed::Box<dyn device_api::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<Box<dyn Device>>,
}
/// 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>,
}
fn iter_dt_probes<'a>() -> impl Iterator<Item = DevTreeProbe<'a>> {
extern "C" {
static __dt_probes_start: u64;
static __dt_probes_end: u64;
}
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);
(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);
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,
}
})
}
}
fn dt_match_compatible(compatible: &str) -> Option<DevTreeProbe> {
iter_dt_probes().find(|probe| probe.compatible.contains(&compatible))
}
/// "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<(&'static dyn Device, DeviceId)> {
// TODO use list, not just the first item
let Some(compatible) =
devtree::find_prop(&dt.node, "compatible").and_then(|prop| prop.str().ok())
else {
return None;
};
let probe = dt_match_compatible(compatible)?;
let device = Box::leak((probe.probe_func)(dt)?);
let id = register_device(device);
Some((device, id))
}
/// 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: Fn(&str, DevTreeNodeInfo) -> Result<(), Error>,
>(
address_cells: usize,
size_cells: usize,
nodes: I,
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) =
devtree::find_prop(&probe.node, "compatible").and_then(|prop| prop.str().ok())
else {
continue;
};
if let Err(error) = f(compatible, probe) {
warnln!("{}: {:?}", name, error);
failed_count += 1;
}
}
if failed_count == 0 {
Ok(())
} else {
Err(failed_count)
}
}
/// 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 {
debugln!("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()
}

@ -11,61 +11,10 @@ use super::{
/// Platform interface for interacting with a general hardware set
pub trait Platform {
/// Interrupt number type for the platform
type IrqNumber;
/// Address, to which the kernel is expected to be loaded for this platform
const KERNEL_PHYS_BASE: usize;
/// Initializes the platform devices to their usable state.
///
/// # Safety
///
/// Unsafe to call if the platform has already been initialized.
unsafe fn init(&'static self, is_bsp: bool) -> Result<(), Error>;
/// Initializes the debug devices to provide early debug output.
///
/// # Safety
///
/// Unsafe to call if the device has already been initialized.
unsafe fn init_debug(&'static self);
/// Returns a display name for the platform
fn name(&self) -> &'static str;
/// Returns a reference to the platform's interrupt controller.
///
/// # Note
///
/// May not be initialized at the moment of calling.
fn interrupt_controller(&self)
-> &dyn ExternalInterruptController<IrqNumber = Self::IrqNumber>;
/// Returns the platform's primary timestamp source.
///
/// # Note
///
/// May not be initialized at the moment of calling.
fn timestamp_source(&self) -> &dyn TimestampSource;
/// Sends a message to the requested set of CPUs through an interprocessor interrupt.
///
/// # Note
///
/// u64 limits the number of targetable CPUs to (only) 64. Platform-specific implementations
/// may impose narrower restrictions.
///
/// # Safety
///
/// As the call may alter the flow of execution on CPUs, this function is unsafe.
unsafe fn send_ipi(&self, target: IpiDeliveryTarget, msg: CpuMessage) -> Result<(), Error>;
/// Performs a CPU reset.
///
/// # Safety
///
/// The caller must ensure it is actually safe to reset, i.e. no critical processes will be
/// aborted and no data will be lost.
unsafe fn reset(&self) -> !;
}

@ -0,0 +1,81 @@
//! ARM PSCI driver implementation
use abi::error::Error;
use alloc::boxed::Box;
use device_api::{CpuBringupDevice, Device};
use fdt_rs::prelude::PropReader;
use crate::{
arch::{aarch64::devtree, ARCHITECTURE},
device_tree_driver,
};
enum CallMethod {
Hvc,
Smc,
}
/// ARM Power State Coordination Interface driver
pub struct Psci {
method: CallMethod,
cpu_on: u32,
#[allow(dead_code)]
cpu_off: u32,
#[allow(dead_code)]
cpu_suspend: u32,
}
impl Device for Psci {
fn display_name(&self) -> &'static str {
"ARM PSCI"
}
unsafe fn init(&'static self) -> Result<(), Error> {
ARCHITECTURE.psci.init(self);
Ok(())
}
}
impl CpuBringupDevice for Psci {
unsafe fn start_cpu(&self, id: usize, ip: usize, arg0: usize) -> Result<(), Error> {
self.call(self.cpu_on as _, id as _, ip as _, arg0 as _);
Ok(())
}
}
impl Psci {
#[inline]
unsafe fn call(&self, mut x0: u64, x1: u64, x2: u64, x3: u64) -> u64 {
match self.method {
CallMethod::Hvc => {
core::arch::asm!("hvc #0", inlateout("x0") x0, in("x1") x1, in("x2") x2, in("x3") x3)
}
CallMethod::Smc => {
core::arch::asm!("smc #0", inlateout("x0") x0, in("x1") x1, in("x2") x2, in("x3") x3)
}
}
x0
}
}
device_tree_driver! {
compatible: ["arm,psci-1.0", "arm,psci"],
probe(dt) => {
let method = devtree::find_prop(&dt.node, "method").and_then(|prop| prop.str().ok())?;
let method = match method {
"hvc" => CallMethod::Hvc,
"smc" => CallMethod::Smc,
_ => panic!("Unknown PSCI call method: {:?}", method)
};
let cpu_on = devtree::find_prop(&dt.node, "cpu_on").and_then(|prop| prop.u32(0).ok())?;
let cpu_off = devtree::find_prop(&dt.node, "cpu_off").and_then(|prop| prop.u32(0).ok())?;
let cpu_suspend = devtree::find_prop(&dt.node, "cpu_suspend").and_then(|prop| prop.u32(0).ok())?;
Some(Box::new(Psci {
method,
cpu_on,
cpu_off,
cpu_suspend
}))
}
}

3
src/device/power/mod.rs Normal file

@ -0,0 +1,3 @@
//! Power-management related device drivers
pub mod arm_psci;

@ -1,16 +1,4 @@
//! Serial device interfaces
use abi::error::Error;
use super::Device;
#[cfg(all(target_arch = "aarch64", not(feature = "aarch64_orange_pi3")))]
pub mod pl011;
/// Generic serial device interface
pub trait SerialDevice: Device {
/// Sends (blocking) a single byte into the serial port
fn send(&self, byte: u8) -> Result<(), Error>;
/// Receive a single byte from the serial port, blocking if necessary
fn receive(&self, blocking: bool) -> Result<u8, Error>;
}
pub mod sunxi_uart;

@ -1,5 +1,11 @@
//! ARM PL011 driver
use abi::{error::Error, io::DeviceRequest};
use alloc::boxed::Box;
use device_api::{
interrupt::{InterruptHandler, IrqNumber},
serial::SerialDevice,
Device,
};
use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
@ -7,16 +13,15 @@ use tock_registers::{
};
use vfs::CharDevice;
use super::SerialDevice;
use crate::{
arch::{aarch64::gic::IrqNumber, PLATFORM},
debug::DebugSink,
device::{
interrupt::InterruptSource,
platform::Platform,
tty::{CharRing, TtyDevice},
Device, InitializableDevice,
arch::{
aarch64::devtree::{self, DevTreeIndexPropExt},
Architecture, ARCHITECTURE,
},
debug::{self, DebugSink, LogLevel},
device::tty::{CharRing, TtyDevice},
device_tree_driver,
fs::devfs::{self, CharDeviceType},
mem::device::DeviceMemoryIo,
sync::IrqSafeSpinlock,
util::OneTimeInit,
@ -81,19 +86,6 @@ impl Pl011Inner {
Ok(())
}
fn recv_byte(&mut self, blocking: bool) -> Result<u8, Error> {
if self.regs.FR.matches_all(FR::RXFE::SET) {
if !blocking {
todo!();
}
while self.regs.FR.matches_all(FR::RXFE::SET) {
core::hint::spin_loop();
}
}
Ok(self.regs.DR.get() as u8)
}
unsafe fn init(&mut self) {
self.regs.CR.set(0);
self.regs.ICR.write(ICR::ALL::CLEAR);
@ -141,68 +133,82 @@ impl SerialDevice for Pl011 {
fn send(&self, byte: u8) -> Result<(), Error> {
self.inner.get().lock().send_byte(byte)
}
fn receive(&self, blocking: bool) -> Result<u8, Error> {
self.inner.get().lock().recv_byte(blocking)
}
}
impl InterruptSource for Pl011 {
unsafe fn init_irq(&'static self) -> Result<(), Error> {
let intc = PLATFORM.interrupt_controller();
intc.register_handler(self.irq, self)?;
self.inner.get().lock().regs.IMSC.modify(IMSC::RXIM::SET);
intc.enable_irq(self.irq)?;
Ok(())
}
fn handle_irq(&self) -> Result<bool, Error> {
impl InterruptHandler for Pl011 {
fn handle_irq(&self) -> bool {
let inner = self.inner.get().lock();
inner.regs.ICR.write(ICR::ALL::CLEAR);
let byte = inner.regs.DR.get();
drop(inner);
self.recv_byte(byte as u8);
if byte == b'\x1b' as u32 {
use crate::task::sched::CpuQueue;
Ok(true)
for (i, queue) in CpuQueue::all().enumerate() {
log_print_raw!(LogLevel::Fatal, "queue{}:\n", i);
let lock = unsafe { queue.grab() };
for item in lock.iter() {
log_print_raw!(
LogLevel::Fatal,
"* {} {:?} {:?}\n",
item.id(),
item.name(),
item.state()
);
}
}
} else {
self.recv_byte(byte as u8);
}
true
}
}
impl InitializableDevice for Pl011 {
type Options = ();
impl Device for Pl011 {
fn display_name(&self) -> &'static str {
"Primecell PL011 UART"
}
unsafe fn init(&self, _opts: ()) -> Result<(), Error> {
unsafe fn init(&'static self) -> Result<(), Error> {
let mut inner = Pl011Inner {
regs: DeviceMemoryIo::map("pl011 UART", self.base)?,
};
inner.init();
self.inner.init(IrqSafeSpinlock::new(inner));
debug::add_sink(self, LogLevel::Debug);
devfs::add_char_device(self, CharDeviceType::TtySerial)?;
Ok(())
}
unsafe fn init_irq(&'static self) -> Result<(), Error> {
let intc = ARCHITECTURE.external_interrupt_controller();
intc.register_irq(self.irq, Default::default(), self)?;
self.inner.get().lock().regs.IMSC.modify(IMSC::RXIM::SET);
intc.enable_irq(self.irq)?;
Ok(())
}
}
impl Device for Pl011 {
fn name(&self) -> &'static str {
"pl011"
}
}
device_tree_driver! {
compatible: ["arm,pl011"],
probe(of) => {
let reg = devtree::find_prop(&of.node, "reg")?;
let (base, _) = reg.cell2_array_item(0, of.address_cells, of.size_cells)?;
impl Pl011 {
/// Constructs an instance of the device at `base`.
///
/// # Safety
///
/// The caller must ensure the address is valid.
pub const unsafe fn new(base: usize, irq: IrqNumber) -> Self {
Self {
Some(Box::new(Pl011 {
inner: OneTimeInit::new(),
// TODO obtain IRQ from dt
irq: IrqNumber::Shared(1),
ring: CharRing::new(),
base,
irq,
}
base: base as usize
}))
}
}

@ -0,0 +1,154 @@
//! Allwinner (H6) UART implementation
use abi::{error::Error, io::DeviceRequest};
use alloc::boxed::Box;
use device_api::{
interrupt::{InterruptHandler, IrqNumber},
serial::SerialDevice,
Device,
};
use tock_registers::{
interfaces::{Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite},
};
use vfs::CharDevice;
use crate::{
arch::{
aarch64::devtree::{self, DevTreeIndexPropExt},
Architecture, ARCHITECTURE,
},
debug::{self, DebugSink, LogLevel},
device::tty::{CharRing, TtyDevice},
device_tree_driver,
fs::devfs::{self, CharDeviceType},
mem::device::DeviceMemoryIo,
sync::IrqSafeSpinlock,
util::OneTimeInit,
};
register_bitfields! {
u32,
USR [
TFE OFFSET(2) NUMBITS(1) [],
TFNF OFFSET(1) NUMBITS(1) []
]
}
register_structs! {
#[allow(non_snake_case)]
Regs {
(0x00 => DLL: ReadWrite<u32>),
(0x04 => _0),
(0x7C => USR: ReadOnly<u32, USR::Register>),
(0x80 => @END),
}
}
struct Inner {
regs: DeviceMemoryIo<Regs>,
}
struct SunxiUart {
inner: OneTimeInit<IrqSafeSpinlock<Inner>>,
base: usize,
irq: IrqNumber,
ring: CharRing<16>,
}
impl DebugSink for SunxiUart {
fn putc(&self, c: u8) -> Result<(), Error> {
self.send(c)
}
}
impl CharDevice for SunxiUart {
fn read(&'static self, _blocking: bool, data: &mut [u8]) -> Result<usize, Error> {
self.line_read(data)
}
fn write(&self, _blocking: bool, data: &[u8]) -> Result<usize, Error> {
self.line_write(data)
}
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
match req {
&mut DeviceRequest::SetTerminalGroup(id) => {
self.set_signal_group(id as _);
Ok(())
}
_ => Err(Error::InvalidArgument),
}
}
}
impl TtyDevice<16> for SunxiUart {
fn ring(&self) -> &CharRing<16> {
&self.ring
}
}
impl InterruptHandler for SunxiUart {
fn handle_irq(&self) -> bool {
todo!()
}
}
impl SerialDevice for SunxiUart {
fn send(&self, byte: u8) -> Result<(), Error> {
let inner = self.inner.get().lock();
if byte == b'\n' {
while inner.regs.USR.matches_all(USR::TFE::CLEAR) {
core::hint::spin_loop();
}
inner.regs.DLL.set(b'\r' as u32);
}
while inner.regs.USR.matches_all(USR::TFE::CLEAR) {
core::hint::spin_loop();
}
inner.regs.DLL.set(byte as u32);
Ok(())
}
}
impl Device for SunxiUart {
fn display_name(&self) -> &'static str {
"Allwinner UART"
}
unsafe fn init(&'static self) -> Result<(), Error> {
let regs = DeviceMemoryIo::<Regs>::map("sunxi-uart", self.base)?;
self.inner.init(IrqSafeSpinlock::new(Inner { regs }));
debug::add_sink(self, LogLevel::Debug);
devfs::add_char_device(self, CharDeviceType::TtySerial)?;
Ok(())
}
unsafe fn init_irq(&'static self) -> Result<(), Error> {
let intc = ARCHITECTURE.external_interrupt_controller();
intc.register_irq(self.irq, Default::default(), self)?;
intc.enable_irq(self.irq)?;
Ok(())
}
}
device_tree_driver! {
compatible: ["snps,dw-apb-uart"],
probe(of) => {
let reg = devtree::find_prop(&of.node, "reg")?;
let (base, _) = reg.cell2_array_item(0, of.address_cells, of.size_cells)?;
if base == 0x05000000 {
Some(Box::new(SunxiUart {
inner: OneTimeInit::new(),
ring: CharRing::new(),
irq: IrqNumber::Shared(0),
base: base as usize
}))
} else {
// TODO don't just hardcode and ignore other UARTs
None
}
}
}

@ -4,6 +4,7 @@ use abi::{
io::{TerminalInputOptions, TerminalLineOptions, TerminalOptions, TerminalOutputOptions},
process::Signal,
};
use device_api::serial::SerialDevice;
use crate::{
proc::wait::Wait,
@ -11,8 +12,6 @@ use crate::{
task::{process::Process, ProcessId},
};
use super::serial::SerialDevice;
#[cfg(feature = "fb_console")]
pub mod combined {
use abi::{error::Error, io::DeviceRequest};

@ -56,6 +56,8 @@ fn setup_root() -> Result<VnodeRef, Error> {
/// This function is meant to be used as a kernel-space process after all the platform-specific
/// initialization has finished.
pub fn kernel_main() {
infoln!("In main");
#[cfg(feature = "fb_console")]
{
use core::time::Duration;

@ -36,6 +36,8 @@ impl DeviceMemory {
let base = ARCHITECTURE.map_device_pages(aligned_base, aligned_size / 0x1000)?;
let base = base + base_offset;
debugln!("Mapped {}@{:#x} to {:#x}", name, phys, base);
Ok(Self { name, base, size })
}

@ -1,6 +1,7 @@
//! Kernel's global heap allocator
use core::{
alloc::{GlobalAlloc, Layout},
ops::Range,
ptr::{null_mut, NonNull},
};
@ -21,13 +22,21 @@ impl KernelAllocator {
unsafe fn init(&self, base: usize, size: usize) {
self.inner.lock().init(base as _, size);
}
fn range(&self) -> Range<usize> {
let lock = self.inner.lock();
lock.bottom() as usize..lock.top() as usize
}
}
unsafe impl GlobalAlloc for KernelAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
match self.inner.lock().allocate_first_fit(layout) {
Ok(v) => v.as_ptr(),
Err(_) => null_mut(),
Err(e) => {
errorln!("Failed to allocate {:?}: {:?}", layout, e);
null_mut()
}
}
}
@ -46,6 +55,10 @@ static GLOBAL_HEAP: KernelAllocator = KernelAllocator::empty();
///
/// The caller must ensure the range is valid and mapped virtual memory.
pub unsafe fn init_heap(heap_base: usize, heap_size: usize) {
debugln!("Heap: {:#x}..{:#x}", heap_base, heap_base + heap_size);
GLOBAL_HEAP.init(heap_base, heap_size);
}
/// Returns the heap address range
pub fn heap_range() -> Range<usize> {
GLOBAL_HEAP.range()
}

@ -7,10 +7,7 @@ use abi::error::Error;
// use abi::error::Error;
//
use crate::{
arch::{Architecture, ArchitectureImpl, PlatformImpl},
device::platform::Platform,
};
use crate::arch::{Architecture, ArchitectureImpl /*, PlatformImpl*/};
use self::table::AddressSpace;
//
@ -22,7 +19,7 @@ pub mod phys;
pub mod table;
/// Kernel's physical load address
pub const KERNEL_PHYS_BASE: usize = PlatformImpl::KERNEL_PHYS_BASE;
// pub const KERNEL_PHYS_BASE: usize = PlatformImpl::KERNEL_PHYS_BASE;
/// Kernel's virtual memory mapping offset (i.e. kernel's virtual address is [KERNEL_PHYS_BASE] +
/// [KERNEL_VIRT_OFFSET])
pub const KERNEL_VIRT_OFFSET: usize = ArchitectureImpl::KERNEL_VIRT_OFFSET;

@ -7,7 +7,7 @@ use crate::{
debug::LogLevel,
mem::{
phys::reserved::{is_reserved, reserve_region},
ConvertAddress, KERNEL_PHYS_BASE,
ConvertAddress, /*, KERNEL_PHYS_BASE */
},
sync::IrqSafeSpinlock,
util::OneTimeInit,
@ -258,12 +258,12 @@ pub unsafe fn init_from_iter<I: Iterator<Item = PhysicalMemoryRegion> + Clone>(
fn kernel_physical_memory_region() -> PhysicalMemoryRegion {
extern "C" {
static __kernel_phys_start: u8;
static __kernel_size: u8;
}
let base = absolute_address!(__kernel_phys_start);
let size = absolute_address!(__kernel_size);
PhysicalMemoryRegion {
base: KERNEL_PHYS_BASE,
size,
}
PhysicalMemoryRegion { base, size }
}

@ -1,10 +1,12 @@
//! Kernel panic handler code
use core::sync::atomic::{AtomicBool, Ordering};
use device_api::interrupt::IpiDeliveryTarget;
use crate::{
arch::{Architecture, ArchitectureImpl},
arch::{Architecture, ArchitectureImpl, CpuMessage, ARCHITECTURE},
debug::{debug_internal, LogLevel},
sync::SpinFence,
sync::{hack_locks, SpinFence},
task::{sched::CpuQueue, Cpu},
};
@ -41,19 +43,19 @@ fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
.compare_exchange(false, true, Ordering::Release, Ordering::Acquire)
.is_ok()
{
// // Let other CPUs know we're screwed
// unsafe {
// PLATFORM
// .send_ipi(IpiDeliveryTarget::AllExceptLocal, CpuMessage::Panic)
// .ok();
// }
// Let other CPUs know we're screwed
unsafe {
ARCHITECTURE
.send_ipi(IpiDeliveryTarget::OtherCpus, CpuMessage::Panic)
.ok();
}
// let ap_count = ArchitectureImpl::cpu_count() - 1;
// PANIC_HANDLED_FENCE.wait_all(ap_count);
let ap_count = ArchitectureImpl::cpu_count() - 1;
PANIC_HANDLED_FENCE.wait_all(ap_count);
// unsafe {
// hack_locks();
// }
unsafe {
hack_locks();
}
log_print_raw!(LogLevel::Fatal, "--- BEGIN PANIC ---\n");
log_print_raw!(LogLevel::Fatal, "In CPU {}\n", Cpu::local_id());

@ -5,8 +5,7 @@ use abi::error::Error;
use alloc::{collections::LinkedList, rc::Rc};
use crate::{
arch::PLATFORM,
device::platform::Platform,
arch::{Architecture, ARCHITECTURE},
sync::IrqSafeSpinlock,
task::process::{Process, ProcessState},
};
@ -124,7 +123,7 @@ impl Wait {
queue_lock = self.queue.lock();
if let Some(deadline) = deadline {
let now = PLATFORM.timestamp_source().timestamp()?;
let now = ARCHITECTURE.monotonic_timer().monotonic_timestamp()?;
if now > deadline {
let mut cursor = queue_lock.cursor_front_mut();
@ -150,7 +149,7 @@ static TICK_LIST: IrqSafeSpinlock<LinkedList<Timeout>> = IrqSafeSpinlock::new(Li
/// Suspends current task until given deadline
pub fn sleep(timeout: Duration, remaining: &mut Duration) -> Result<(), Error> {
static SLEEP_NOTIFY: Wait = Wait::new("sleep");
let now = PLATFORM.timestamp_source().timestamp()?;
let now = ARCHITECTURE.monotonic_timer().monotonic_timestamp()?;
let deadline = now + timeout;
match SLEEP_NOTIFY.wait(Some(deadline)) {

@ -79,7 +79,7 @@ pub fn init() -> Result<(), Error> {
let cpu_count = ArchitectureImpl::cpu_count();
// Create a queue for each CPU
sched::init_queues(Vec::from_iter((0..cpu_count).map(|_| CpuQueue::new())));
sched::init_queues(Vec::from_iter((0..cpu_count).map(CpuQueue::new)));
spawn_kernel_closure("[kmain]", kernel_main)?;
// spawn_kernel_closure(move || loop {

@ -59,6 +59,8 @@ struct ProcessInner {
session_terminal: Option<VnodeRef>,
signal_entry: Option<SignalEntry>,
signal_stack: VecDeque<Signal>,
queue: Option<&'static CpuQueue>,
}
/// Process data and state structure
@ -110,6 +112,8 @@ impl Process {
session_terminal: None,
signal_entry: None,
signal_stack: VecDeque::new(),
queue: None,
}),
io: IrqSafeSpinlock::new(ProcessIo::new()),
});
@ -230,8 +234,14 @@ impl Process {
/// # Panics
///
/// Currently, the code will panic if the process is queued/executing on any queue.
pub fn enqueue_to(self: Rc<Self>, queue: &CpuQueue) {
pub fn enqueue_to(self: Rc<Self>, queue: &'static CpuQueue) {
let _irq = IrqGuard::acquire();
{
let mut inner = self.inner.lock();
let old_queue = inner.queue.replace(queue);
assert!(old_queue.is_none());
}
let current_state = self.state.swap(ProcessState::Ready, Ordering::SeqCst);
if current_state == ProcessState::Terminated {
@ -243,15 +253,16 @@ impl Process {
}
}
/// Marks the process as suspended, blocking it from being run until it's resumed.
///
/// # Note
///
/// The process may not halt its execution immediately when this function is called, only when
/// this function is called targeting the *current process* running on *local* CPU.
pub fn suspend(&self) {
fn dequeue(&self, new_state: ProcessState) {
let _irq = IrqGuard::acquire();
let current_state = self.state.swap(ProcessState::Suspended, Ordering::SeqCst);
assert_ne!(new_state, ProcessState::Ready);
assert_ne!(new_state, ProcessState::Running);
let mut inner = self.inner.lock();
let current_state = self.state.swap(new_state, Ordering::SeqCst);
let proc_queue = inner.queue.take().unwrap();
proc_queue.dequeue(self.id());
match current_state {
// NOTE: I'm not sure if the process could've been queued between the store and this
@ -267,6 +278,8 @@ impl Process {
let queue = Cpu::local().queue();
if cpu_id == local_cpu_id {
assert_eq!(queue as *const _, proc_queue as *const _, "Process queue mismatch: process says cpu{}, queue {:p}, actual cpu{}, queue {:p}", cpu_id, proc_queue, local_cpu_id, queue);
drop(inner);
// Suspending a process running on local CPU
unsafe { queue.yield_cpu() }
} else {
@ -276,6 +289,16 @@ impl Process {
}
}
/// Marks the process as suspended, blocking it from being run until it's resumed.
///
/// # Note
///
/// The process may not halt its execution immediately when this function is called, only when
/// this function is called targeting the *current process* running on *local* CPU.
pub fn suspend(&self) {
self.dequeue(ProcessState::Suspended);
}
/// Returns current wait status of the task
pub fn wait_status(&self) -> WaitStatus {
self.inner.lock().wait_status
@ -319,9 +342,7 @@ impl Process {
/// Handles the cleanup of an exited process
pub fn handle_exit(&self) {
// Queue lock is still held
assert_eq!(self.state(), ProcessState::Terminated);
// Scheduler still holds a lock of this process?
// TODO cancel Wait if a process was killed while suspended?
{
let inner = self.inner.lock();
@ -422,21 +443,25 @@ impl CurrentProcess {
/// Terminate the current process
pub fn exit(&self, status: ExitCode) {
self.inner.lock().exit_status = status.into();
let current_state = self.state.swap(ProcessState::Terminated, Ordering::SeqCst);
assert_eq!(current_state, ProcessState::Running);
debugln!("Process {} exited with code {:?}", self.id(), status);
match current_state {
ProcessState::Suspended => {
todo!();
}
ProcessState::Ready => todo!(),
ProcessState::Running => {
self.handle_exit();
unsafe { Cpu::local().queue().yield_cpu() }
}
ProcessState::Terminated => todo!(),
}
self.dequeue(ProcessState::Terminated);
// let current_state = self.state.swap(ProcessState::Terminated, Ordering::SeqCst);
// assert_eq!(current_state, ProcessState::Running);
// match current_state {
// ProcessState::Suspended => {
// todo!();
// }
// ProcessState::Ready => todo!(),
// ProcessState::Running => {
// self.handle_exit();
// unsafe { Cpu::local().queue().yield_cpu() }
// }
// ProcessState::Terminated => todo!(),
// }
}
/// Sets up a return frame to handle a pending signal, if any is present in the task's queue.

@ -44,6 +44,7 @@ pub struct CpuQueueInner {
pub struct CpuQueue {
inner: IrqSafeSpinlock<CpuQueueInner>,
idle: TaskContext,
index: usize,
}
static QUEUES: OneTimeInit<Vec<CpuQueue>> = OneTimeInit::new();
@ -87,18 +88,13 @@ impl CpuQueueInner {
ProcessState::Ready => {
return Some(task);
}
// Drop suspended tasks from the queue
ProcessState::Suspended | ProcessState::Terminated => (),
ProcessState::Running => {
// TODO fix this finally
}
// e => panic!(
// "Unexpected process state in CpuQueue: {:?} ({} {:?}, cpu_id={})",
// e,
// task.id(),
// task.name(),
// task.cpu_id()
// ),
e => panic!(
"Unexpected process state in CpuQueue: {:?} ({} {:?}, cpu_id={})",
e,
task.id(),
task.name(),
task.cpu_id()
),
}
}
@ -114,7 +110,7 @@ impl CpuQueueInner {
impl CpuQueue {
/// Constructs an empty queue with its own idle task
pub fn new() -> Self {
pub fn new(index: usize) -> Self {
let idle = TaskContext::kernel(__idle, Cpu::local_id() as usize)
.expect("Could not construct an idle task");
@ -127,6 +123,7 @@ impl CpuQueue {
})
},
idle,
index,
}
}
@ -164,8 +161,8 @@ impl CpuQueue {
if let Some(current) = current.as_ref() {
if current.state() == ProcessState::Running {
current.set_state(ProcessState::Ready);
}
inner.queue.push_back(current.clone());
}
// inner.stats.cpu_time += delta;
} else {
@ -209,6 +206,7 @@ impl CpuQueue {
// log_print_raw!(crate::debug::LogLevel::Info, "\n");
assert!(ArchitectureImpl::interrupt_mask());
to.switch(from)
}
@ -219,13 +217,19 @@ impl CpuQueue {
/// Only meant to be called from Process impl. The function does not set any process accounting
/// information, which may lead to invalid states.
pub unsafe fn enqueue(&self, p: Rc<Process>) {
let mut inner = self.inner.lock();
assert!(ArchitectureImpl::interrupt_mask());
assert_eq!(p.state(), ProcessState::Ready);
self.inner.lock().queue.push_back(p);
inner.queue.push_back(p);
}
/// Removes process with given PID from the exeuction queue.
pub fn dequeue(&self, _pid: ProcessId) {
todo!();
pub fn dequeue(&self, pid: ProcessId) {
assert!(ArchitectureImpl::interrupt_mask());
let mut inner = self.inner.lock();
inner.queue.retain(|p| p.id() != pid)
}
/// Returns the queue length at this moment.