Red Bear OS kernel baseline

From release 0.1.0 pre-patched archive.
This includes all Red Bear modifications previously maintained
as patches in local/patches/kernel/.
This commit is contained in:
Red Bear OS
2026-06-27 09:19:25 +03:00
commit 82feefbaee
234 changed files with 40494 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
use alloc::boxed::Box;
use super::{find_sdt, sdt::Sdt};
use crate::{
arch::device::generic_timer::GenericTimer,
dtb::irqchip::{register_irq, IRQ_CHIP},
};
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct Gtdt {
pub header: Sdt,
pub cnt_control_base: u64,
_reserved: u32,
pub secure_el1_timer_gsiv: u32,
pub secure_el1_timer_flags: u32,
pub non_secure_el1_timer_gsiv: u32,
pub non_secure_el1_timer_flags: u32,
pub virtual_el1_timer_gsiv: u32,
pub virtual_el1_timer_flags: u32,
pub el2_timer_gsiv: u32,
pub el2_timer_flags: u32,
pub cnt_read_base: u64,
pub platform_timer_count: u32,
pub platform_timer_offset: u32,
/*TODO: we don't need these yet, and they cause short tables to fail parsing
pub virtual_el2_timer_gsiv: u32,
pub virtual_el2_timer_flags: u32,
*/
//TODO: platform timer structure (at platform timer offset, with platform timer count)
}
impl Gtdt {
pub fn init() {
let gtdt_sdt = find_sdt("GTDT");
let gtdt = if gtdt_sdt.len() == 1 {
match Gtdt::new(gtdt_sdt[0]) {
Some(gtdt) => gtdt,
None => {
warn!("Failed to parse GTDT");
return;
}
}
} else {
warn!("Unable to find GTDT");
return;
};
let gsiv = gtdt.non_secure_el1_timer_gsiv;
info!("generic_timer gsiv = {}", gsiv);
let mut timer = GenericTimer::new();
timer.init();
register_irq(gsiv, Box::new(timer));
unsafe { IRQ_CHIP.irq_enable(gsiv as u32) };
}
pub fn new(sdt: &'static Sdt) -> Option<&'static Gtdt> {
if &sdt.signature == b"GTDT" && sdt.length as usize >= size_of::<Gtdt>() {
Some(unsafe { &*((sdt as *const Sdt) as *const Gtdt) })
} else {
None
}
}
}
+121
View File
@@ -0,0 +1,121 @@
use core::ptr::{self, read_volatile, write_volatile};
#[cfg(not(target_arch = "x86"))]
use crate::memory::{RmmA, RmmArch};
use crate::{find_one_sdt, memory::PhysicalAddress};
use super::{sdt::Sdt, GenericAddressStructure, ACPI_TABLE};
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct Hpet {
pub header: Sdt,
pub hw_rev_id: u8,
pub comparator_descriptor: u8,
pub pci_vendor_id: u16,
pub base_address: GenericAddressStructure,
pub hpet_number: u8,
pub min_periodic_clk_tick: u16,
pub oem_attribute: u8,
}
impl Hpet {
pub fn init() {
let hpet = Hpet::new(find_one_sdt!("HPET"));
if let Some(hpet) = hpet {
debug!(" HPET: {:X}", hpet.hpet_number);
let mut hpet_t = ACPI_TABLE.hpet.write();
*hpet_t = Some(hpet);
}
}
pub fn new(sdt: &'static Sdt) -> Option<Hpet> {
if &sdt.signature == b"HPET" && sdt.length as usize >= size_of::<Hpet>() {
let s = unsafe { ptr::read((sdt as *const Sdt) as *const Hpet) };
if s.base_address.address_space == 0 {
unsafe { s.map() };
Some(s)
} else {
warn!(
"HPET has unsupported address space {}",
s.base_address.address_space
);
None
}
} else {
None
}
}
}
//TODO: x86 use assumes only one HPET and only one GenericAddressStructure
#[cfg(target_arch = "x86")]
impl Hpet {
pub unsafe fn map(&self) {
unsafe {
use crate::memory::{Frame, KernelMapper, Page, PageFlags, VirtualAddress};
let frame = Frame::containing(PhysicalAddress::new(self.base_address.address as usize));
let page = Page::containing_address(VirtualAddress::new(crate::HPET_OFFSET));
KernelMapper::lock_rw()
.map_phys(
page.start_address(),
frame.base(),
PageFlags::new().write(true).device_memory(true),
)
.expect("failed to map memory for GenericAddressStructure")
.flush();
}
}
pub unsafe fn read_u64(&self, offset: usize) -> u64 {
unsafe { read_volatile((crate::HPET_OFFSET + offset) as *const u64) }
}
pub unsafe fn write_u64(&mut self, offset: usize, value: u64) {
unsafe {
write_volatile((crate::HPET_OFFSET + offset) as *mut u64, value);
}
}
}
#[cfg(not(target_arch = "x86"))]
impl Hpet {
pub unsafe fn map(&self) {
unsafe {
crate::memory::map_device_memory(
PhysicalAddress::new(self.base_address.address as usize),
crate::memory::PAGE_SIZE,
);
}
}
pub unsafe fn read_u64(&self, offset: usize) -> u64 {
unsafe {
read_volatile(
RmmA::phys_to_virt(PhysicalAddress::new(
self.base_address.address as usize + offset,
))
.data() as *const u64,
)
}
}
pub unsafe fn write_u64(&mut self, offset: usize, value: u64) {
unsafe {
write_volatile(
RmmA::phys_to_virt(PhysicalAddress::new(
self.base_address.address as usize + offset,
))
.data() as *mut u64,
value,
);
}
}
}
+97
View File
@@ -0,0 +1,97 @@
use alloc::{boxed::Box, vec::Vec};
use super::{Madt, MadtEntry};
use crate::{
arch::device::irqchip::{
gic::{GenericInterruptController, GicCpuIf, GicDistIf},
gicv3::{GicV3, GicV3CpuIf},
},
dtb::irqchip::{IrqChipItem, IRQ_CHIP},
memory::{map_device_memory, PhysicalAddress, PAGE_SIZE},
};
pub(super) fn init(madt: Madt) {
let mut gicd_opt = None;
let mut giccs = Vec::new();
for madt_entry in madt.iter() {
debug!(" {:#x?}", madt_entry);
match madt_entry {
MadtEntry::Gicc(gicc) => {
giccs.push(gicc);
}
MadtEntry::Gicd(gicd) => {
if gicd_opt.is_some() {
warn!("Only one GICD should be present on a system, ignoring this one");
} else {
gicd_opt = Some(gicd);
}
}
_ => {}
}
}
let Some(gicd) = gicd_opt else {
warn!("No GICD found");
return;
};
let mut gic_dist_if = GicDistIf::default();
unsafe {
let phys = PhysicalAddress::new(gicd.physical_base_address as usize);
let virt = map_device_memory(phys, PAGE_SIZE);
gic_dist_if.init(virt.data());
};
info!("{:#x?}", gic_dist_if);
match gicd.gic_version {
1 | 2 => {
for gicc in giccs {
let mut gic_cpu_if = GicCpuIf::default();
unsafe {
let phys = PhysicalAddress::new(gicc.physical_base_address as usize);
let virt = map_device_memory(phys, PAGE_SIZE);
gic_cpu_if.init(virt.data())
};
info!("{:#x?}", gic_cpu_if);
let gic = GenericInterruptController {
gic_dist_if,
gic_cpu_if,
irq_range: (0, 0),
};
let chip = IrqChipItem {
phandle: 0,
parents: Vec::new(),
children: Vec::new(),
ic: Box::new(gic),
};
unsafe { IRQ_CHIP.irq_chip_list.chips.push(chip) };
//TODO: support more GICCs
break;
}
}
3 => {
for gicc in giccs {
let mut gic_cpu_if = GicV3CpuIf;
unsafe { gic_cpu_if.init() };
info!("{:#x?}", gic_cpu_if);
let gic = GicV3 {
gic_dist_if,
gic_cpu_if,
//TODO: get GICRs
gicrs: Vec::new(),
irq_range: (0, 0),
};
let chip = IrqChipItem {
phandle: 0,
parents: Vec::new(),
children: Vec::new(),
ic: Box::new(gic),
};
unsafe { IRQ_CHIP.irq_chip_list.chips.push(chip) };
//TODO: support more GICCs
break;
}
}
_ => {
warn!("unsupported GIC version {}", gicd.gic_version);
}
}
unsafe { IRQ_CHIP.init(None) };
}
+9
View File
@@ -0,0 +1,9 @@
use super::Madt;
pub(super) fn init(madt: Madt) {
for madt_entry in madt.iter() {
debug!(" {:#x?}", madt_entry);
}
warn!("MADT not yet handled on this platform");
}
+160
View File
@@ -0,0 +1,160 @@
use core::{
hint,
sync::atomic::{AtomicU8, Ordering},
};
use crate::{
arch::{
device::local_apic::the_local_apic,
start::{kstart_ap, KernelArgsAp},
},
cpu_set::LogicalCpuId,
memory::{
allocate_p2frame, Frame, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch,
VirtualAddress, PAGE_SIZE,
},
startup::AP_READY,
};
use super::{Madt, MadtEntry};
const TRAMPOLINE: usize = 0x8000;
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
pub(super) fn init(madt: Madt) {
let local_apic = unsafe { the_local_apic() };
let me = local_apic.id();
if local_apic.x2 {
debug!(" X2APIC {}", me.get());
} else {
debug!(" XAPIC {}: {:>08X}", me.get(), local_apic.address);
}
if cfg!(not(feature = "multi_core")) {
return;
}
// Map trampoline
let trampoline_frame = Frame::containing(PhysicalAddress::new(TRAMPOLINE));
let trampoline_page = Page::containing_address(VirtualAddress::new(TRAMPOLINE));
let (result, page_table_physaddr) = unsafe {
//TODO: do not have writable and executable!
let mut mapper = KernelMapper::lock_rw();
let result = mapper
.map_phys(
trampoline_page.start_address(),
trampoline_frame.base(),
PageFlags::new().execute(true).write(true),
)
.expect("failed to map trampoline");
(result, mapper.table().phys().data())
};
result.flush();
// Write trampoline, make sure TRAMPOLINE page is free for use
for (i, val) in TRAMPOLINE_DATA.iter().enumerate() {
unsafe {
(*((TRAMPOLINE as *mut u8).add(i) as *const AtomicU8)).store(*val, Ordering::SeqCst);
}
}
unsafe {
let preliminary_cpu_count = madt.iter().filter(|e| matches!(e, MadtEntry::LocalApic(entry) if u32::from(entry.id) == me.get() || entry.flags & 1 == 1)).count();
crate::profiling::allocate(preliminary_cpu_count as u32);
}
for madt_entry in madt.iter() {
debug!(" {:x?}", madt_entry);
if let MadtEntry::LocalApic(ap_local_apic) = madt_entry {
if u32::from(ap_local_apic.id) == me.get() {
debug!(" This is my local APIC");
} else if ap_local_apic.flags & 1 == 1 {
let cpu_id = LogicalCpuId::next();
// Allocate a stack
let stack_start = RmmA::phys_to_virt(
allocate_p2frame(4)
.expect("no more frames in acpi stack_start")
.base(),
)
.data();
let stack_end = stack_start + (PAGE_SIZE << 4);
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
let args = KernelArgsAp {
stack_end: stack_end as *mut u8,
cpu_id,
pcr_ptr,
idt_ptr,
};
let ap_ready = (TRAMPOLINE + 8) as *mut u64;
let ap_args_ptr = unsafe { ap_ready.add(1) };
let ap_page_table = unsafe { ap_ready.add(2) };
let ap_code = unsafe { ap_ready.add(3) };
// Set the ap_ready to 0, volatile
unsafe {
ap_ready.write(0);
ap_args_ptr.write(&args as *const _ as u64);
ap_page_table.write(page_table_physaddr as u64);
#[expect(clippy::fn_to_numeric_cast)]
ap_code.write(kstart_ap as u64);
// TODO: Is this necessary (this fence)?
core::arch::asm!("");
};
AP_READY.store(false, Ordering::SeqCst);
// Send INIT IPI
{
let mut icr = 0x4500;
if local_apic.x2 {
icr |= u64::from(ap_local_apic.id) << 32;
} else {
icr |= u64::from(ap_local_apic.id) << 56;
}
local_apic.set_icr(icr);
}
// Send START IPI
{
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
let mut icr = 0x4600 | ap_segment as u64;
if local_apic.x2 {
icr |= u64::from(ap_local_apic.id) << 32;
} else {
icr |= u64::from(ap_local_apic.id) << 56;
}
local_apic.set_icr(icr);
}
// Wait for trampoline ready
while unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } == 0 {
hint::spin_loop();
}
while !AP_READY.load(Ordering::SeqCst) {
hint::spin_loop();
}
RmmA::invalidate_all();
}
}
}
// Unmap trampoline
let (_frame, _, flush) = unsafe {
KernelMapper::lock_rw()
.unmap_phys(trampoline_page.start_address())
.expect("failed to unmap trampoline page")
};
flush.flush();
}
+240
View File
@@ -0,0 +1,240 @@
use core::cell::SyncUnsafeCell;
use super::sdt::Sdt;
use crate::find_one_sdt;
/// The Multiple APIC Descriptor Table
#[derive(Clone, Copy, Debug)]
pub struct Madt {
sdt: &'static Sdt,
pub local_address: u32,
pub flags: u32,
}
#[cfg(target_arch = "aarch64")]
#[path = "arch/aarch64.rs"]
mod arch;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[path = "arch/x86.rs"]
mod arch;
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))]
#[path = "arch/other.rs"]
mod arch;
static MADT: SyncUnsafeCell<Option<Madt>> = SyncUnsafeCell::new(None);
pub fn madt() -> Option<&'static Madt> {
unsafe { &*MADT.get() }.as_ref()
}
pub const FLAG_PCAT: u32 = 1;
impl Madt {
pub fn init() {
let madt = Madt::new(find_one_sdt!("APIC"));
if let Some(madt) = madt {
// safe because no APs have been started yet.
unsafe { MADT.get().write(Some(madt)) };
debug!(" APIC: {:>08X}: {}", madt.local_address, madt.flags);
arch::init(madt);
}
}
pub fn new(sdt: &'static Sdt) -> Option<Madt> {
if &sdt.signature == b"APIC" && sdt.data_len() >= 8 {
//Not valid if no local address and flags
let local_address = unsafe { (sdt.data_address() as *const u32).read_unaligned() };
let flags = unsafe {
(sdt.data_address() as *const u32)
.offset(1)
.read_unaligned()
};
Some(Madt {
sdt,
local_address,
flags,
})
} else {
None
}
}
pub fn iter(&self) -> MadtIter {
MadtIter {
sdt: self.sdt,
i: 8, // Skip local controller address and flags
}
}
}
/// MADT Local APIC
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtLocalApic {
/// Processor ID
pub processor: u8,
/// Local APIC ID
pub id: u8,
/// Flags. 1 means that the processor is enabled
pub flags: u32,
}
/// MADT I/O APIC
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtIoApic {
/// I/O APIC ID
pub id: u8,
/// reserved
_reserved: u8,
/// I/O APIC address
pub address: u32,
/// Global system interrupt base
pub gsi_base: u32,
}
/// MADT Interrupt Source Override
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtIntSrcOverride {
/// Bus Source
pub bus_source: u8,
/// IRQ Source
pub irq_source: u8,
/// Global system interrupt base
pub gsi_base: u32,
/// Flags
pub flags: u16,
}
/// MADT GICC
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtGicc {
_reserved: u16,
pub cpu_interface_number: u32,
pub acpi_processor_uid: u32,
pub flags: u32,
pub parking_protocol_version: u32,
pub performance_interrupt_gsiv: u32,
pub parked_address: u64,
pub physical_base_address: u64,
pub gicv: u64,
pub gich: u64,
pub vgic_maintenance_interrupt: u32,
pub gicr_base_address: u64,
pub mpidr: u64,
pub processor_power_efficiency_class: u8,
_reserved2: u8,
pub spe_overflow_interrupt: u16,
//TODO: optional field introduced in ACPI 6.5: pub trbe_interrupt: u16,
}
/// MADT GICD
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MadtGicd {
_reserved: u16,
pub gic_id: u32,
pub physical_base_address: u64,
pub system_vector_base: u32,
pub gic_version: u8,
_reserved2: [u8; 3],
}
/// MADT Entries
#[derive(Debug)]
#[allow(dead_code)]
pub enum MadtEntry {
LocalApic(&'static MadtLocalApic),
InvalidLocalApic(usize),
IoApic(&'static MadtIoApic),
InvalidIoApic(usize),
IntSrcOverride(&'static MadtIntSrcOverride),
InvalidIntSrcOverride(usize),
Gicc(&'static MadtGicc),
InvalidGicc(usize),
Gicd(&'static MadtGicd),
InvalidGicd(usize),
Unknown(u8),
}
pub struct MadtIter {
sdt: &'static Sdt,
i: usize,
}
impl Iterator for MadtIter {
type Item = MadtEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.i + 1 < self.sdt.data_len() {
let entry_type = unsafe { *(self.sdt.data_address() as *const u8).add(self.i) };
let entry_len =
unsafe { *(self.sdt.data_address() as *const u8).add(self.i + 1) } as usize;
if self.i + entry_len <= self.sdt.data_len() {
let item = match entry_type {
0x0 => {
if entry_len == size_of::<MadtLocalApic>() + 2 {
MadtEntry::LocalApic(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtLocalApic)
})
} else {
MadtEntry::InvalidLocalApic(entry_len)
}
}
0x1 => {
if entry_len == size_of::<MadtIoApic>() + 2 {
MadtEntry::IoApic(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtIoApic)
})
} else {
MadtEntry::InvalidIoApic(entry_len)
}
}
0x2 => {
if entry_len == size_of::<MadtIntSrcOverride>() + 2 {
MadtEntry::IntSrcOverride(unsafe {
&*((self.sdt.data_address() + self.i + 2)
as *const MadtIntSrcOverride)
})
} else {
MadtEntry::InvalidIntSrcOverride(entry_len)
}
}
0xB => {
if entry_len >= size_of::<MadtGicc>() + 2 {
MadtEntry::Gicc(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtGicc)
})
} else {
MadtEntry::InvalidGicc(entry_len)
}
}
0xC => {
if entry_len >= size_of::<MadtGicd>() + 2 {
MadtEntry::Gicd(unsafe {
&*((self.sdt.data_address() + self.i + 2) as *const MadtGicd)
})
} else {
MadtEntry::InvalidGicd(entry_len)
}
}
_ => MadtEntry::Unknown(entry_type),
};
self.i += entry_len;
Some(item)
} else {
None
}
} else {
None
}
}
}
+212
View File
@@ -0,0 +1,212 @@
//! # ACPI
//! Code to parse the ACPI tables
use alloc::{boxed::Box, string::String, vec::Vec};
use hashbrown::HashMap;
use spin::{Once, RwLock};
use crate::memory::{KernelMapper, PageFlags, PhysicalAddress, RmmA, RmmArch};
use self::{hpet::Hpet, madt::Madt, rsdp::Rsdp, rsdt::Rsdt, rxsdt::Rxsdt, sdt::Sdt, xsdt::Xsdt};
#[cfg(target_arch = "aarch64")]
mod gtdt;
pub mod hpet;
pub mod madt;
mod rsdp;
mod rsdt;
mod rxsdt;
pub mod sdt;
#[cfg(target_arch = "aarch64")]
mod spcr;
mod xsdt;
unsafe fn map_linearly(addr: PhysicalAddress, len: usize, mapper: &mut crate::memory::PageMapper) {
unsafe {
let base = PhysicalAddress::new(crate::memory::round_down_pages(addr.data()));
let aligned_len = crate::memory::round_up_pages(len + (addr.data() - base.data()));
for page_idx in 0..aligned_len / crate::memory::PAGE_SIZE {
let (_, flush) = mapper
.map_linearly(
base.add(page_idx * crate::memory::PAGE_SIZE),
PageFlags::new(),
)
.expect("failed to linearly map SDT");
flush.flush();
}
}
}
pub fn get_sdt(sdt_address: PhysicalAddress, mapper: &mut KernelMapper<true>) -> &'static Sdt {
let sdt;
unsafe {
const SDT_SIZE: usize = size_of::<Sdt>();
map_linearly(sdt_address, SDT_SIZE, mapper);
sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt);
map_linearly(
sdt_address.add(SDT_SIZE),
sdt.length as usize - SDT_SIZE,
mapper,
);
}
sdt
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
pub struct GenericAddressStructure {
pub address_space: u8,
pub bit_width: u8,
pub bit_offset: u8,
pub access_size: u8,
pub address: u64,
}
pub enum RxsdtEnum {
Rsdt(Rsdt),
Xsdt(Xsdt),
}
impl Rxsdt for RxsdtEnum {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>> {
match self {
Self::Rsdt(rsdt) => <Rsdt as Rxsdt>::iter(rsdt),
Self::Xsdt(xsdt) => <Xsdt as Rxsdt>::iter(xsdt),
}
}
}
pub static RXSDT_ENUM: Once<RxsdtEnum> = Once::new();
/// Parse the ACPI tables to gather CPU, interrupt, and timer information
pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) {
unsafe {
{
let mut sdt_ptrs = SDT_POINTERS.write();
*sdt_ptrs = Some(HashMap::new());
}
// Search for RSDP
let rsdp_opt = Rsdp::get_rsdp(already_supplied_rsdp);
if let Some(rsdp) = rsdp_opt {
debug!("SDT address: {:#x}", rsdp.sdt_address().data());
let rxsdt = get_sdt(rsdp.sdt_address(), &mut KernelMapper::lock_rw());
let rxsdt = if let Some(rsdt) = Rsdt::new(rxsdt) {
let mut initialized = false;
let rsdt = RXSDT_ENUM.call_once(|| {
initialized = true;
RxsdtEnum::Rsdt(rsdt)
});
if !initialized {
error!("RXSDT_ENUM already initialized");
}
rsdt
} else if let Some(xsdt) = Xsdt::new(rxsdt) {
let mut initialized = false;
let xsdt = RXSDT_ENUM.call_once(|| {
initialized = true;
RxsdtEnum::Xsdt(xsdt)
});
if !initialized {
error!("RXSDT_ENUM already initialized");
}
xsdt
} else {
warn!("UNKNOWN RSDT OR XSDT SIGNATURE");
return;
};
// TODO: Don't touch ACPI tables in kernel?
for sdt in rxsdt.iter() {
get_sdt(sdt, &mut KernelMapper::lock_rw());
}
for sdt_address in rxsdt.iter() {
let sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt);
let signature = get_sdt_signature(sdt);
if let Some(ref mut ptrs) = *(SDT_POINTERS.write()) {
ptrs.insert(signature, sdt);
}
}
// TODO: Enumerate processors in userspace, and then provide an ACPI-independent interface
// to initialize enumerated processors to userspace?
Madt::init();
//TODO: support this on any arch
// SPCR must be initialized after MADT for interrupt controllers
#[cfg(target_arch = "aarch64")]
spcr::Spcr::init();
// TODO: Let userspace setup HPET, and then provide an interface to specify which timer to
// use?
Hpet::init();
#[cfg(target_arch = "aarch64")]
gtdt::Gtdt::init();
} else {
error!("NO RSDP FOUND");
}
}
}
pub type SdtSignature = (String, [u8; 6], [u8; 8]);
pub static SDT_POINTERS: RwLock<Option<HashMap<SdtSignature, &'static Sdt>>> = RwLock::new(None);
pub fn find_sdt(name: &str) -> Vec<&'static Sdt> {
let mut sdts: Vec<&'static Sdt> = vec![];
if let Some(ref ptrs) = *(SDT_POINTERS.read()) {
for (signature, sdt) in ptrs {
if signature.0 == name {
sdts.push(sdt);
}
}
}
sdts
}
#[macro_export]
macro_rules! find_one_sdt {
($name:expr) => {{
use $crate::acpi::find_sdt;
match find_sdt($name).as_slice() {
[] => {
println!("Unable to find {}", $name);
return;
}
[x] => *x,
x => {
println!("{} {} found, expected 1", x.len(), $name);
return;
}
}
}};
}
pub fn get_sdt_signature(sdt: &'static Sdt) -> SdtSignature {
let signature =
String::from_utf8(sdt.signature.to_vec()).expect("Error converting signature to string");
(signature, sdt.oem_id, sdt.oem_table_id)
}
pub struct Acpi {
pub hpet: RwLock<Option<Hpet>>,
}
pub static ACPI_TABLE: Acpi = Acpi {
hpet: RwLock::new(None),
};
+34
View File
@@ -0,0 +1,34 @@
use rmm::PhysicalAddress;
/// RSDP
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Rsdp {
signature: [u8; 8],
_checksum: u8,
_oemid: [u8; 6],
revision: u8,
rsdt_address: u32,
_length: u32,
xsdt_address: u64,
_extended_checksum: u8,
_reserved: [u8; 3],
}
impl Rsdp {
pub unsafe fn get_rsdp(already_supplied_rsdp: Option<*const u8>) -> Option<Rsdp> {
already_supplied_rsdp.map(|rsdp_ptr| {
// TODO: Validate
unsafe { *(rsdp_ptr as *const Rsdp) }
})
}
/// Get the RSDT or XSDT address
pub fn sdt_address(&self) -> PhysicalAddress {
PhysicalAddress::new(if self.revision >= 2 {
self.xsdt_address as usize
} else {
self.rsdt_address as usize
})
}
}
+52
View File
@@ -0,0 +1,52 @@
use alloc::boxed::Box;
use core::convert::TryFrom;
use rmm::PhysicalAddress;
use super::{rxsdt::Rxsdt, sdt::Sdt};
#[derive(Debug)]
pub struct Rsdt(&'static Sdt);
impl Rsdt {
pub fn new(sdt: &'static Sdt) -> Option<Rsdt> {
if &sdt.signature == b"RSDT" {
Some(Rsdt(sdt))
} else {
None
}
}
pub fn as_slice(&self) -> &[u8] {
let length =
usize::try_from(self.0.length).expect("expected 32-bit length to fit within usize");
unsafe { core::slice::from_raw_parts(self.0 as *const _ as *const u8, length) }
}
}
impl Rxsdt for Rsdt {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>> {
Box::new(RsdtIter { sdt: self.0, i: 0 })
}
}
pub struct RsdtIter {
sdt: &'static Sdt,
i: usize,
}
impl Iterator for RsdtIter {
type Item = PhysicalAddress;
fn next(&mut self) -> Option<Self::Item> {
if self.i < self.sdt.data_len() / size_of::<u32>() {
let item = unsafe {
(self.sdt.data_address() as *const u32)
.add(self.i)
.read_unaligned()
};
self.i += 1;
Some(PhysicalAddress::new(item as usize))
} else {
None
}
}
}
+6
View File
@@ -0,0 +1,6 @@
use alloc::boxed::Box;
use rmm::PhysicalAddress;
pub trait Rxsdt {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>>;
}
+27
View File
@@ -0,0 +1,27 @@
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Sdt {
pub signature: [u8; 4],
pub length: u32,
pub revision: u8,
pub checksum: u8,
pub oem_id: [u8; 6],
pub oem_table_id: [u8; 8],
pub oem_revision: u32,
pub creator_id: u32,
pub creator_revision: u32,
}
impl Sdt {
/// Get the address of this tables data
pub fn data_address(&self) -> usize {
self as *const _ as usize + size_of::<Sdt>()
}
/// Get the length of this tables data
pub fn data_len(&self) -> usize {
let total_size = self.length as usize;
let header_size = size_of::<Sdt>();
total_size.saturating_sub(header_size)
}
}
+140
View File
@@ -0,0 +1,140 @@
use super::{find_sdt, sdt::Sdt, GenericAddressStructure};
use crate::{
arch::device::serial::COM1,
devices::{serial::SerialKind, uart_pl011},
log::LOG,
memory::{map_device_memory, PhysicalAddress, PAGE_SIZE},
};
const INTERRUPT_TYPE_8259: u8 = 1 << 0;
const INTERRUPT_TYPE_APIC: u8 = 1 << 1;
const INTERRUPT_TYPE_SAPIC: u8 = 1 << 2;
const INTERRUPT_TYPE_GIC: u8 = 1 << 3;
const INTERRUPT_TYPE_PLIC: u8 = 1 << 4;
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct Spcr {
pub header: Sdt,
pub interface_type: u8,
_reserved: [u8; 3],
pub base_address: GenericAddressStructure,
pub interrupt_type: u8,
pub irq: u8,
pub gsiv: u32,
pub configured_baud_rate: u8,
pub parity: u8,
pub stop_bits: u8,
pub flow_control: u8,
pub terminal_type: u8,
pub language: u8,
pub pci_device_id: u16,
pub pci_vendor_id: u16,
pub pci_bus: u8,
pub pci_device: u8,
pub pci_function: u8,
pub pci_flags: u32,
pub pci_segment: u8,
/*TODO: these fields are optional based on the table revision
pub uart_clock_frequency: u32,
pub precise_baud_rate: u32,
pub namespace_string_length: u16,
pub namespace_string_offset: u16,
*/
// namespace_string
}
impl Spcr {
pub fn init() {
let spcr_sdt = find_sdt("SPCR");
let spcr = if spcr_sdt.len() == 1 {
match Spcr::new(spcr_sdt[0]) {
Some(spcr) => spcr,
None => {
warn!("Failed to parse SPCR");
return;
}
}
} else {
warn!("Unable to find SPCR");
return;
};
if spcr.base_address.address == 0 {
// Serial disabled
return;
}
let serial_was_empty = !matches!(*COM1.lock(), SerialKind::NotPresent);
if spcr.header.revision >= 2 {
match spcr.interface_type {
3 => {
// PL011
if spcr.base_address.address_space == 0
&& spcr.base_address.bit_width == 32
&& spcr.base_address.bit_offset == 0
&& spcr.base_address.access_size == 3
{
let virt = unsafe {
map_device_memory(
PhysicalAddress::new(spcr.base_address.address as usize),
PAGE_SIZE,
)
};
let serial_port = uart_pl011::SerialPort::new(virt.data(), false);
*COM1.lock() = SerialKind::Pl011(serial_port);
//TODO: enable IRQ on more platforms and interrupt types
if (spcr.interrupt_type & INTERRUPT_TYPE_GIC) == INTERRUPT_TYPE_GIC {
#[cfg(target_arch = "aarch64")]
unsafe {
crate::arch::device::serial::init_acpi(spcr.gsiv);
}
}
} else {
warn!(
"SPCR unsuppoted address for PL011 {:#x?}",
spcr.base_address
);
}
}
//TODO: support more types!
unsupported => {
warn!(
"SPCR revision {} unsupported interface type {}",
spcr.header.revision, unsupported
);
}
}
} else if spcr.header.revision == 1 {
match spcr.interface_type {
//TODO: support more types!
unsupported => {
warn!("SPCR revision 1 unsupported interface type {}", unsupported);
}
}
} else {
warn!("SPCR unsupported revision {}", spcr.header.revision);
}
let mut serial_port = COM1.lock();
if serial_was_empty && !matches!(*serial_port, SerialKind::NotPresent) {
// backfill logs since the heap is loaded
if let Some(ref mut early_log) = *LOG.lock() {
let (s1, s2) = early_log.read();
if !s1.is_empty() {
serial_port.write(s1);
}
if !s2.is_empty() {
serial_port.write(s2);
}
}
}
}
pub fn new(sdt: &'static Sdt) -> Option<&'static Spcr> {
if &sdt.signature == b"SPCR" && sdt.length as usize >= size_of::<Spcr>() {
Some(unsafe { &*((sdt as *const Sdt) as *const Spcr) })
} else {
None
}
}
}
+50
View File
@@ -0,0 +1,50 @@
use alloc::boxed::Box;
use core::convert::TryFrom;
use rmm::PhysicalAddress;
use super::{rxsdt::Rxsdt, sdt::Sdt};
#[derive(Debug)]
pub struct Xsdt(&'static Sdt);
impl Xsdt {
pub fn new(sdt: &'static Sdt) -> Option<Xsdt> {
if &sdt.signature == b"XSDT" {
Some(Xsdt(sdt))
} else {
None
}
}
pub fn as_slice(&self) -> &[u8] {
let length =
usize::try_from(self.0.length).expect("expected 32-bit length to fit within usize");
unsafe { core::slice::from_raw_parts(self.0 as *const _ as *const u8, length) }
}
}
impl Rxsdt for Xsdt {
fn iter(&self) -> Box<dyn Iterator<Item = PhysicalAddress>> {
Box::new(XsdtIter { sdt: self.0, i: 0 })
}
}
pub struct XsdtIter {
sdt: &'static Sdt,
i: usize,
}
impl Iterator for XsdtIter {
type Item = PhysicalAddress;
fn next(&mut self) -> Option<Self::Item> {
if self.i < self.sdt.data_len() / size_of::<u64>() {
let item = unsafe {
core::ptr::read_unaligned((self.sdt.data_address() as *const u64).add(self.i))
};
self.i += 1;
Some(PhysicalAddress::new(item as usize))
} else {
None
}
}
}
+50
View File
@@ -0,0 +1,50 @@
use crate::memory::KernelMapper;
use core::{
alloc::{GlobalAlloc, Layout},
ptr::NonNull,
};
use linked_list_allocator::Heap;
use spin::Mutex;
static HEAP: Mutex<Option<Heap>> = Mutex::new(None);
pub struct Allocator;
impl Allocator {
pub unsafe fn init(offset: usize, size: usize) {
unsafe {
*HEAP.lock() = Some(Heap::new(offset, size));
}
}
}
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe {
while let Some(ref mut heap) = *HEAP.lock() {
match heap.allocate_first_fit(layout) {
Ok(ptr) => return ptr.as_ptr(),
Err(()) => {
let size = heap.size();
super::map_heap(
&mut KernelMapper::lock_rw(),
crate::kernel_heap_offset() + size,
super::KERNEL_HEAP_SIZE,
);
heap.extend(super::KERNEL_HEAP_SIZE);
}
}
}
panic!("__rust_allocate: heap not initialized");
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe {
HEAP.lock()
.as_mut()
.expect("heap not initialized")
.deallocate(NonNull::new_unchecked(ptr), layout)
}
}
}
+48
View File
@@ -0,0 +1,48 @@
use crate::memory::{KernelMapper, Page, PageFlags, VirtualAddress};
use rmm::{Flusher, FrameAllocator, PageFlushAll};
pub use self::linked_list::Allocator;
mod linked_list;
/// Size of kernel heap
const KERNEL_HEAP_SIZE: usize = ::rmm::MEGABYTE;
unsafe fn map_heap(mapper: &mut KernelMapper<true>, offset: usize, size: usize) {
let mut flush_all = PageFlushAll::new();
let heap_start_page = Page::containing_address(VirtualAddress::new(offset));
let heap_end_page = Page::containing_address(VirtualAddress::new(offset + size - 1));
for page in Page::range_inclusive(heap_start_page, heap_end_page) {
let phys = mapper
.allocator_mut()
.allocate_one()
.expect("failed to allocate kernel heap");
let flush = unsafe {
mapper
.map_phys(
page.start_address(),
phys,
PageFlags::new()
.write(true)
.global(cfg!(not(feature = "pti"))),
)
.expect("failed to map kernel heap")
};
flush_all.consume(flush);
}
flush_all.flush();
}
pub unsafe fn init() {
unsafe {
let offset = crate::kernel_heap_offset();
let size = KERNEL_HEAP_SIZE;
// Map heap pages
map_heap(&mut KernelMapper::lock_rw(), offset, size);
// Initialize global heap
Allocator::init(offset, size);
}
}
+15
View File
@@ -0,0 +1,15 @@
// Because the memory map is so important to not be aliased, it is defined here, in one place
// The lower 256 PML4 entries are reserved for userspace
// Each PML4 entry references up to 512 GB of memory
// The second from the top (510) PML4 is reserved for the kernel
/// The size of a single PML4
pub const PML4_SIZE: usize = 0x0000_0080_0000_0000;
/// Offset to kernel heap
#[inline(always)]
pub fn kernel_heap_offset() -> usize {
crate::kernel_executable_offsets::KERNEL_OFFSET() - PML4_SIZE
}
/// End offset of the user image, i.e. kernel start
pub const USER_END_OFFSET: usize = 256 * PML4_SIZE;
+19
View File
@@ -0,0 +1,19 @@
use spin::MutexGuard;
use crate::{arch::device::serial::COM1, devices::serial::SerialKind};
pub struct Writer<'a> {
serial: MutexGuard<'a, SerialKind>,
}
impl<'a> Writer<'a> {
pub fn new() -> Writer<'a> {
Writer {
serial: COM1.lock(),
}
}
pub fn write(&mut self, buf: &[u8]) {
self.serial.write(buf);
}
}
+277
View File
@@ -0,0 +1,277 @@
use core::fmt::{Result, Write};
use crate::arch::device::cpu::registers::{control_regs, id_regs};
pub mod registers;
bitfield::bitfield! {
pub struct MachineId(u32);
get_implementer, _: 31, 24;
get_variant, _: 23, 20;
get_architecture, _: 19, 16;
get_part_number, _: 15, 4;
get_revision, _: 3, 0;
}
enum ImplementerID {
Unknown,
Arm,
Broadcom,
Cavium,
Digital,
Fujitsu,
Infineon,
Motorola,
Nvidia,
AMCC,
Qualcomm,
Marvell,
Intel,
Ampere,
}
const IMPLEMENTERS: [&'static str; 14] = [
"Unknown", "Arm", "Broadcom", "Cavium", "Digital", "Fujitsu", "Infineon", "Motorola", "Nvidia",
"AMCC", "Qualcomm", "Marvell", "Intel", "Ampere",
];
enum VariantID {
Unknown,
}
const VARIANTS: [&'static str; 1] = ["Unknown"];
enum ArchitectureID {
Unknown,
V4,
V4T,
V5,
V5T,
V5TE,
V5TEJ,
V6,
}
const ARCHITECTURES: [&'static str; 8] =
["Unknown", "v4", "v4T", "v5", "v5T", "v5TE", "v5TEJ", "v6"];
enum PartNumberID {
Unknown,
Thunder,
Foundation,
CortexA35,
CortexA53,
CortexA55,
CortexA57,
CortexA72,
CortexA73,
CortexA75,
}
const PART_NUMBERS: [&'static str; 10] = [
"Unknown",
"Thunder",
"Foundation",
"Cortex-A35",
"Cortex-A53",
"Cortex-A55",
"Cortex-A57",
"Cortex-A72",
"Cortex-A73",
"Cortex-A75",
];
enum RevisionID {
Unknown,
Thunder1_0,
Thunder1_1,
}
const REVISIONS: [&'static str; 3] = ["Unknown", "Thunder-1.0", "Thunder-1.1"];
struct CpuInfo {
implementer: &'static str,
variant: &'static str,
architecture: &'static str,
part_number: &'static str,
revision: &'static str,
aa64isar0: id_regs::AA64Isar0,
aa64isar1: id_regs::AA64Isar1,
}
impl CpuInfo {
fn new() -> CpuInfo {
let midr = unsafe { control_regs::midr() };
let midr = MachineId(midr);
let implementer = match midr.get_implementer() {
0x41 => IMPLEMENTERS[ImplementerID::Arm as usize],
0x42 => IMPLEMENTERS[ImplementerID::Broadcom as usize],
0x43 => IMPLEMENTERS[ImplementerID::Cavium as usize],
0x44 => IMPLEMENTERS[ImplementerID::Digital as usize],
0x46 => IMPLEMENTERS[ImplementerID::Fujitsu as usize],
0x49 => IMPLEMENTERS[ImplementerID::Infineon as usize],
0x4d => IMPLEMENTERS[ImplementerID::Motorola as usize],
0x4e => IMPLEMENTERS[ImplementerID::Nvidia as usize],
0x50 => IMPLEMENTERS[ImplementerID::AMCC as usize],
0x51 => IMPLEMENTERS[ImplementerID::Qualcomm as usize],
0x56 => IMPLEMENTERS[ImplementerID::Marvell as usize],
0x69 => IMPLEMENTERS[ImplementerID::Intel as usize],
0xc0 => IMPLEMENTERS[ImplementerID::Ampere as usize],
_ => IMPLEMENTERS[ImplementerID::Unknown as usize],
};
let variant = match midr.get_variant() {
_ => VARIANTS[VariantID::Unknown as usize],
};
let architecture = match midr.get_architecture() {
0b0001 => ARCHITECTURES[ArchitectureID::V4 as usize],
0b0010 => ARCHITECTURES[ArchitectureID::V4T as usize],
0b0011 => ARCHITECTURES[ArchitectureID::V5 as usize],
0b0100 => ARCHITECTURES[ArchitectureID::V5T as usize],
0b0101 => ARCHITECTURES[ArchitectureID::V5TE as usize],
0b0110 => ARCHITECTURES[ArchitectureID::V5TEJ as usize],
0b0111 => ARCHITECTURES[ArchitectureID::V6 as usize],
_ => ARCHITECTURES[ArchitectureID::Unknown as usize],
};
let part_number = match midr.get_part_number() {
0x0a1 => PART_NUMBERS[PartNumberID::Thunder as usize],
0xd00 => PART_NUMBERS[PartNumberID::Foundation as usize],
0xd04 => PART_NUMBERS[PartNumberID::CortexA35 as usize],
0xd03 => PART_NUMBERS[PartNumberID::CortexA53 as usize],
0xd05 => PART_NUMBERS[PartNumberID::CortexA55 as usize],
0xd07 => PART_NUMBERS[PartNumberID::CortexA57 as usize],
0xd08 => PART_NUMBERS[PartNumberID::CortexA72 as usize],
0xd09 => PART_NUMBERS[PartNumberID::CortexA73 as usize],
0xd0a => PART_NUMBERS[PartNumberID::CortexA75 as usize],
_ => PART_NUMBERS[PartNumberID::Unknown as usize],
};
let revision = match part_number {
"Thunder" => {
let val = match midr.get_revision() {
0x00 => REVISIONS[RevisionID::Thunder1_0 as usize],
0x01 => REVISIONS[RevisionID::Thunder1_1 as usize],
_ => REVISIONS[RevisionID::Unknown as usize],
};
val
}
_ => REVISIONS[RevisionID::Unknown as usize],
};
let aa64isar0 = id_regs::aa64isar0();
let aa64isar1 = id_regs::aa64isar1();
CpuInfo {
implementer,
variant,
architecture,
part_number,
revision,
aa64isar0,
aa64isar1,
}
}
}
pub fn cpu_info<W: Write>(w: &mut W) -> Result {
let cpuinfo = CpuInfo::new();
writeln!(w, "Implementer: {}", cpuinfo.implementer)?;
writeln!(w, "Variant: {}", cpuinfo.variant)?;
writeln!(w, "Architecture version: {}", cpuinfo.architecture)?;
writeln!(w, "Part Number: {}", cpuinfo.part_number)?;
writeln!(w, "Revision: {}", cpuinfo.revision)?;
// Print detected CPU features.
// Follow the naming convention estabilished by `std::arch::is_aarch64_feature_detected`.
write!(w, "Features:")?;
// ID_AA64ISAR0_EL1
if cpuinfo.aa64isar0.has_feat_rng() {
write!(w, " rand")?;
}
if cpuinfo.aa64isar0.has_feat_flagm() {
write!(w, " flagm")?;
}
if cpuinfo.aa64isar0.has_feat_flagm2() {
write!(w, " flagm2")?;
}
if cpuinfo.aa64isar0.has_feat_fhm() {
write!(w, " fhm")?;
}
if cpuinfo.aa64isar0.has_feat_dotprod() {
write!(w, " dotprod")?;
}
if cpuinfo.aa64isar0.has_feat_sm3() && cpuinfo.aa64isar0.has_feat_sm4() {
write!(w, " sm4")?;
}
if cpuinfo.aa64isar0.has_feat_sha512() && cpuinfo.aa64isar0.has_feat_sha3() {
write!(w, " sha3")?;
}
if cpuinfo.aa64isar0.has_feat_rdm() {
write!(w, " rdm")?;
}
if cpuinfo.aa64isar0.has_feat_lse() {
write!(w, " lse")?;
}
if cpuinfo.aa64isar0.has_feat_lse128() {
write!(w, " lse128")?;
}
if cpuinfo.aa64isar0.has_feat_crc() {
write!(w, " crc")?;
}
if cpuinfo.aa64isar0.has_feat_sha1() && cpuinfo.aa64isar0.has_feat_sha256() {
write!(w, " sha2")?;
}
if cpuinfo.aa64isar0.has_feat_aes() && cpuinfo.aa64isar0.has_feat_pmull() {
write!(w, " aes")?;
}
// ID_AA64ISAR1_EL1
if cpuinfo.aa64isar1.has_feat_i8mm() {
write!(w, " i8mm")?;
}
if cpuinfo.aa64isar1.has_feat_bf16() {
write!(w, " bf16")?;
}
if cpuinfo.aa64isar1.has_feat_sb() {
write!(w, " sb")?;
}
if cpuinfo.aa64isar1.has_feat_frintts() {
write!(w, " frintts")?;
}
if cpuinfo.aa64isar1.gpi() != 0 || cpuinfo.aa64isar1.gpa() != 0 {
write!(w, " pacg")?;
}
if cpuinfo.aa64isar1.has_feat_lrcpc() {
write!(w, " rcpc")?;
}
if cpuinfo.aa64isar1.has_feat_lrcpc2() {
write!(w, " rcpc2")?;
}
if cpuinfo.aa64isar1.has_feat_lrcpc3() {
write!(w, " rcpc3")?;
}
if cpuinfo.aa64isar1.has_feat_fcma() {
write!(w, " fcma")?;
}
if cpuinfo.aa64isar1.has_feat_jscvt() {
write!(w, " jsconv")?;
}
if cpuinfo.aa64isar1.api() != 0 || cpuinfo.aa64isar1.apa() != 0 {
write!(w, " paca")?;
}
if cpuinfo.aa64isar1.has_feat_dpb() {
write!(w, " dpb")?;
}
if cpuinfo.aa64isar1.has_feat_dpb2() {
write!(w, " dpb2")?;
}
writeln!(w)?;
Ok(())
}
@@ -0,0 +1,167 @@
#![allow(unused)]
//! Functions to read and write control registers.
use core::arch::asm;
pub unsafe fn ttbr0_el1() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, ttbr0_el1", out(reg) ret);
ret
}
}
pub unsafe fn ttbr0_el1_write(val: u64) {
unsafe {
asm!("msr ttbr0_el1, {}", in(reg) val);
}
}
pub unsafe fn ttbr1_el1() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, ttbr1_el1", out(reg) ret);
ret
}
}
pub unsafe fn ttbr1_el1_write(val: u64) {
unsafe {
asm!("msr ttbr1_el1, {}", in(reg) val);
}
}
pub unsafe fn tpidr_el0() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, tpidr_el0", out(reg) ret);
ret
}
}
pub unsafe fn tpidr_el0_write(val: u64) {
unsafe {
asm!("msr tpidr_el0, {}", in(reg) val);
}
}
pub unsafe fn tpidr_el1() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, tpidr_el1", out(reg) ret);
ret
}
}
pub unsafe fn tpidr_el1_write(val: u64) {
unsafe {
asm!("msr tpidr_el1, {}", in(reg) val);
}
}
pub unsafe fn tpidrro_el0() -> u64 {
unsafe {
let ret: u64;
asm!("mrs {}, tpidrro_el0", out(reg) ret);
ret
}
}
pub unsafe fn tpidrro_el0_write(val: u64) {
unsafe {
asm!("msr tpidrro_el0, {}", in(reg) val);
}
}
pub unsafe fn esr_el1() -> u32 {
unsafe {
let ret: u32;
asm!("mrs {0:w}, esr_el1", out(reg) ret);
ret
}
}
pub unsafe fn vhe_present() -> bool {
unsafe {
let mut mmfr1: u64;
asm!("mrs {}, id_aa64mmfr1_el1", out(reg) mmfr1);
// The VHE (Virtualization Host Extensions) field is in bits [7:4].
let vhe_field = (mmfr1 >> 4) & 0b1111;
vhe_field != 0
}
}
pub unsafe fn cntfrq_el0() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, cntfrq_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn ptmr_ctrl() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, cntp_ctl_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn ptmr_ctrl_write(val: u32) {
unsafe {
asm!("msr cntp_ctl_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn ptmr_tval() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {0}, cntp_tval_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn ptmr_tval_write(val: u32) {
unsafe {
asm!("msr cntp_tval_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn vtmr_ctrl() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, cntv_ctl_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn vtmr_ctrl_write(val: u32) {
unsafe {
asm!("msr cntv_ctl_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn vtmr_tval() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {0}, cntv_tval_el0", out(reg) ret);
ret as u32
}
}
pub unsafe fn vtmr_tval_write(val: u32) {
unsafe {
asm!("msr cntv_tval_el0, {}", in(reg) val as usize);
}
}
pub unsafe fn midr() -> u32 {
unsafe {
let ret: usize;
asm!("mrs {}, midr_el1", out(reg) ret);
ret as u32
}
}
@@ -0,0 +1,151 @@
//! Functions and bitfield definitions for `ID_AA64*` system registers. (e.g. `ID_AA64ISAR0_EL1`)
use core::arch::asm;
bitfield::bitfield! {
pub struct AA64Isar0(u64);
impl Debug;
pub rndr, _: 63, 60;
pub tlb, _: 59, 56;
pub ts, _: 55, 52;
pub fhm, _: 51, 48;
pub dp, _: 47, 44;
pub sm4, _: 43, 40;
pub sm3, _: 39, 36;
pub sha3, _: 35, 32;
pub rdm, _: 31, 28;
pub atomic, _: 23, 20;
pub crc32, _: 19, 16;
pub sha2, _: 15, 12;
pub sha1, _: 11, 8;
pub aes, _: 7, 4;
}
bitfield::bitfield! {
pub struct AA64Isar1(u64);
impl Debug;
pub ls64, _: 63, 60;
pub xs, _: 59, 56;
pub i8mm, _: 55, 52;
pub dgh, _: 51, 48;
pub bf16, _: 47, 44;
pub specres, _: 43, 40;
pub sb, _: 39, 36;
pub frintts, _: 35, 32;
pub gpi, _: 31, 28;
pub gpa, _: 27, 24;
pub lrcpc, _: 23, 20;
pub fcma, _: 19, 16;
pub jscvt, _: 15, 12;
pub api, _: 11, 8;
pub apa, _: 7, 4;
pub dpb, _: 3, 0;
}
impl AA64Isar0 {
pub fn has_feat_rng(&self) -> bool {
self.rndr() == 0b0001
}
pub fn has_feat_flagm(&self) -> bool {
self.ts() == 0b0001
}
pub fn has_feat_flagm2(&self) -> bool {
self.ts() == 0b0010
}
pub fn has_feat_fhm(&self) -> bool {
self.fhm() == 0b0001
}
pub fn has_feat_dotprod(&self) -> bool {
self.dp() == 0b0001
}
pub fn has_feat_sm4(&self) -> bool {
self.sm4() == 0b0001
}
pub fn has_feat_sm3(&self) -> bool {
self.sm3() == 0b0001
}
pub fn has_feat_sha3(&self) -> bool {
self.sha3() == 0b0001
}
pub fn has_feat_rdm(&self) -> bool {
self.rdm() == 0b0001
}
pub fn has_feat_lse(&self) -> bool {
self.atomic() == 0b0010
}
pub fn has_feat_lse128(&self) -> bool {
self.atomic() == 0b0011
}
/// The current Arm Architecture Registers Manual calls it FEAT_CRC32,
/// but everyone else seems to call it FEAT_CRC.
pub fn has_feat_crc(&self) -> bool {
self.crc32() == 0b0001
}
pub fn has_feat_sha256(&self) -> bool {
self.sha2() == 0b0001
}
pub fn has_feat_sha512(&self) -> bool {
self.sha2() == 0b0010
}
pub fn has_feat_sha1(&self) -> bool {
self.sha1() == 0b0001
}
pub fn has_feat_aes(&self) -> bool {
self.aes() == 0b0001
}
pub fn has_feat_pmull(&self) -> bool {
self.aes() == 0b0010
}
}
impl AA64Isar1 {
pub fn has_feat_i8mm(&self) -> bool {
self.i8mm() == 0b0001
}
pub fn has_feat_bf16(&self) -> bool {
self.bf16() == 0b0001
}
pub fn has_feat_sb(&self) -> bool {
self.sb() == 0b0001
}
pub fn has_feat_frintts(&self) -> bool {
self.frintts() == 0b0001
}
pub fn has_feat_lrcpc(&self) -> bool {
self.lrcpc() == 0b0001
}
pub fn has_feat_lrcpc2(&self) -> bool {
self.lrcpc() == 0b0010
}
pub fn has_feat_lrcpc3(&self) -> bool {
self.lrcpc() == 0b0011
}
pub fn has_feat_fcma(&self) -> bool {
self.fcma() == 0b0001
}
pub fn has_feat_jscvt(&self) -> bool {
self.jscvt() == 0b0011
}
pub fn has_feat_dpb(&self) -> bool {
self.dpb() == 0b0001
}
pub fn has_feat_dpb2(&self) -> bool {
self.dpb() == 0b0010
}
}
pub fn aa64isar0() -> AA64Isar0 {
let ret: u64;
unsafe {
asm!("mrs {}, ID_AA64ISAR0_EL1", out(reg) ret);
}
AA64Isar0(ret)
}
pub fn aa64isar1() -> AA64Isar1 {
let ret: u64;
unsafe {
asm!("mrs {}, ID_AA64ISAR1_EL1", out(reg) ret);
}
AA64Isar1(ret)
}
@@ -0,0 +1,2 @@
pub mod control_regs;
pub mod id_regs;
+145
View File
@@ -0,0 +1,145 @@
use alloc::boxed::Box;
use super::ic_for_chip;
use crate::{
arch::device::cpu::registers::control_regs,
context::{self, timeout},
dtb::{
get_interrupt,
irqchip::{register_irq, InterruptHandler, IRQ_CHIP},
},
scheme::irq::irq_trigger,
sync::CleanLockToken,
time,
};
use fdt::Fdt;
bitflags! {
struct TimerCtrlFlags: u32 {
const ENABLE = 1 << 0;
const IMASK = 1 << 1;
const ISTATUS = 1 << 2;
}
}
pub unsafe fn init(fdt: &Fdt) {
unsafe {
let mut timer = GenericTimer::new();
timer.init();
if let Some(node) = fdt.find_compatible(&["arm,armv7-timer"]) {
let irq = get_interrupt(fdt, &node, 1).unwrap();
debug!("irq = {:?}", irq);
if let Some(ic_idx) = ic_for_chip(&fdt, &node) {
//PHYS_NONSECURE_PPI only
let virq = IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_xlate(irq)
.unwrap();
info!("generic_timer virq = {}", virq);
register_irq(virq as u32, Box::new(timer));
IRQ_CHIP.irq_enable(virq as u32);
} else {
error!("Failed to find irq parent for generic timer");
}
}
}
}
pub struct GenericTimer {
pub use_virtual_timer: bool,
pub clk_freq: u32,
pub reload_count: u32,
}
impl GenericTimer {
pub fn new() -> Self {
Self {
use_virtual_timer: false,
clk_freq: 0,
reload_count: 0,
}
}
pub fn init(&mut self) {
self.use_virtual_timer = unsafe { !control_regs::vhe_present() };
debug!(
"generic_timer use_virtual_timer = {:?}",
self.use_virtual_timer
);
let clk_freq = unsafe { control_regs::cntfrq_el0() };
self.clk_freq = clk_freq;
self.reload_count = clk_freq / 100;
self.reload_count();
}
fn read_tmr_ctrl(&self) -> TimerCtrlFlags {
TimerCtrlFlags::from_bits_truncate(if self.use_virtual_timer {
unsafe { control_regs::vtmr_ctrl() }
} else {
unsafe { control_regs::ptmr_ctrl() }
})
}
fn write_tmr_ctrl(&self, ctrl: TimerCtrlFlags) {
if self.use_virtual_timer {
unsafe { control_regs::vtmr_ctrl_write(ctrl.bits()) };
} else {
unsafe { control_regs::ptmr_ctrl_write(ctrl.bits()) };
}
}
#[allow(unused)]
fn disable(&self) {
let mut ctrl = self.read_tmr_ctrl();
ctrl.remove(TimerCtrlFlags::ENABLE);
self.write_tmr_ctrl(ctrl);
}
#[allow(unused)]
pub fn set_irq(&mut self) {
let mut ctrl = self.read_tmr_ctrl();
ctrl.remove(TimerCtrlFlags::IMASK);
self.write_tmr_ctrl(ctrl);
}
pub fn clear_irq(&mut self) {
let mut ctrl = self.read_tmr_ctrl();
if ctrl.contains(TimerCtrlFlags::ISTATUS) {
ctrl.insert(TimerCtrlFlags::IMASK);
self.write_tmr_ctrl(ctrl);
}
}
pub fn reload_count(&mut self) {
if self.use_virtual_timer {
unsafe { control_regs::vtmr_tval_write(self.reload_count) };
} else {
unsafe { control_regs::ptmr_tval_write(self.reload_count) };
}
let mut ctrl = self.read_tmr_ctrl();
ctrl.insert(TimerCtrlFlags::ENABLE);
ctrl.remove(TimerCtrlFlags::IMASK);
self.write_tmr_ctrl(ctrl);
}
}
impl InterruptHandler for GenericTimer {
fn irq_handler(&mut self, irq: u32, token: &mut CleanLockToken) {
self.clear_irq();
{
*time::OFFSET.write(token.token()) += self.clk_freq as u128;
}
timeout::trigger(token);
context::switch::tick(token);
unsafe {
// FIXME add_irq accepts a u8 as irq number
// PercpuBlock::current().stats.add_irq(irq);
irq_trigger(irq.try_into().unwrap(), token);
IRQ_CHIP.irq_eoi(irq);
}
self.reload_count();
}
}
+288
View File
@@ -0,0 +1,288 @@
use super::InterruptController;
use crate::{
dtb::{
get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc},
},
sync::CleanLockToken,
};
use core::ptr::{read_volatile, write_volatile};
use fdt::{node::FdtNode, Fdt};
use syscall::{
error::{Error, EINVAL},
Result,
};
static GICD_CTLR: u32 = 0x000;
static GICD_TYPER: u32 = 0x004;
static GICD_ISENABLER: u32 = 0x100;
static GICD_ICENABLER: u32 = 0x180;
static GICD_IPRIORITY: u32 = 0x400;
static GICD_ITARGETSR: u32 = 0x800;
static GICD_ICFGR: u32 = 0xc00;
static GICC_EOIR: u32 = 0x0010;
static GICC_IAR: u32 = 0x000c;
static GICC_CTLR: u32 = 0x0000;
static GICC_PMR: u32 = 0x0004;
pub struct GenericInterruptController {
pub gic_dist_if: GicDistIf,
pub gic_cpu_if: GicCpuIf,
pub irq_range: (usize, usize),
}
impl GenericInterruptController {
pub fn new() -> Self {
let gic_dist_if = GicDistIf::default();
let gic_cpu_if = GicCpuIf::default();
GenericInterruptController {
gic_dist_if,
gic_cpu_if,
irq_range: (0, 0),
}
}
pub fn parse(fdt: &Fdt) -> Result<(usize, usize, usize, usize)> {
if let Some(node) = fdt.find_compatible(&["arm,cortex-a15-gic", "arm,gic-400"]) {
return GenericInterruptController::parse_inner(fdt, &node);
} else {
return Err(Error::new(EINVAL));
}
}
fn parse_inner(fdt: &Fdt, node: &FdtNode) -> Result<(usize, usize, usize, usize)> {
//assert address_cells == 0x2, size_cells == 0x2
let reg = node.reg().unwrap();
let mut regs = (0, 0, 0, 0);
let mut idx = 0;
for chunk in reg {
if chunk.size.is_none() {
break;
}
let addr = get_mmio_address(fdt, node, &chunk).unwrap();
match idx {
0 => (regs.0, regs.1) = (addr, chunk.size.unwrap()),
2 => (regs.2, regs.3) = (addr, chunk.size.unwrap()),
_ => break,
}
idx += 2;
}
if idx == 4 {
Ok(regs)
} else {
Err(Error::new(EINVAL))
}
}
}
impl InterruptHandler for GenericInterruptController {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for GenericInterruptController {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
if let Some(fdt) = fdt_opt {
let (dist_addr, _dist_size, cpu_addr, _cpu_size) =
match GenericInterruptController::parse(fdt) {
Ok(regs) => regs,
Err(err) => return Err(err),
};
unsafe {
self.gic_dist_if.init(crate::PHYS_OFFSET + dist_addr);
self.gic_cpu_if.init(crate::PHYS_OFFSET + cpu_addr);
}
}
let idx = *irq_idx;
let cnt = if self.gic_dist_if.nirqs > 1024 {
1024
} else {
self.gic_dist_if.nirqs as usize
};
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("gic irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
Ok(())
}
fn irq_ack(&mut self) -> u32 {
unsafe { self.gic_cpu_if.irq_ack() }
}
fn irq_eoi(&mut self, irq_num: u32) {
unsafe { self.gic_cpu_if.irq_eoi(irq_num) }
}
fn irq_enable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_enable(irq_num) }
}
fn irq_disable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_disable(irq_num) }
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
let off = match irq_data {
IrqCell::L3(0, irq, _flags) => irq as usize + 32, // SPI
IrqCell::L3(1, irq, _flags) => irq as usize + 16, // PPI
_ => return Err(Error::new(EINVAL)),
};
return Ok(off + self.irq_range.0);
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq >= self.gic_dist_if.nirqs {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
#[derive(Debug, Default)]
pub struct GicDistIf {
pub address: usize,
pub ncpus: u32,
pub nirqs: u32,
}
impl GicDistIf {
pub unsafe fn init(&mut self, addr: usize) {
unsafe {
self.address = addr;
// Disable IRQ Distribution
self.write(GICD_CTLR, 0);
let typer = self.read(GICD_TYPER);
self.ncpus = ((typer & (0x7 << 5)) >> 5) + 1;
self.nirqs = ((typer & 0x1f) + 1) * 32;
info!(
"gic: Distributor supports {:?} CPUs and {:?} IRQs",
self.ncpus, self.nirqs
);
// Set all SPIs to level triggered
for irq in (32..self.nirqs).step_by(16) {
self.write(GICD_ICFGR + ((irq / 16) * 4), 0);
}
// Disable all SPIs
for irq in (32..self.nirqs).step_by(32) {
self.write(GICD_ICENABLER + ((irq / 32) * 4), 0xffff_ffff);
}
// Affine all SPIs to CPU0 and set priorities for all IRQs
for irq in 0..self.nirqs {
if irq > 31 {
let ext_offset = GICD_ITARGETSR + (4 * (irq / 4));
let int_offset = irq % 4;
let mut val = self.read(ext_offset);
val |= 0b0000_0001 << (8 * int_offset);
self.write(ext_offset, val);
}
let ext_offset = GICD_IPRIORITY + (4 * (irq / 4));
let int_offset = irq % 4;
let mut val = self.read(ext_offset);
val |= 0b0000_0000 << (8 * int_offset);
self.write(ext_offset, val);
}
// Enable IRQ group 0 and group 1 non-secure distribution
self.write(GICD_CTLR, 0x3);
}
}
pub unsafe fn irq_enable(&mut self, irq: u32) {
unsafe {
let offset = GICD_ISENABLER + (4 * (irq / 32));
let shift = 1 << (irq % 32);
let mut val = self.read(offset);
val |= shift;
self.write(offset, val);
}
}
pub unsafe fn irq_disable(&mut self, irq: u32) {
unsafe {
let offset = GICD_ICENABLER + (4 * (irq / 32));
let shift = 1 << (irq % 32);
let mut val = self.read(offset);
val |= shift;
self.write(offset, val);
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
#[derive(Debug, Default)]
pub struct GicCpuIf {
pub address: usize,
}
impl GicCpuIf {
pub unsafe fn init(&mut self, addr: usize) {
unsafe {
self.address = addr;
// Enable CPU0's GIC interface
self.write(GICC_CTLR, 1);
// Set CPU0's Interrupt Priority Mask
self.write(GICC_PMR, 0xff);
}
}
unsafe fn irq_ack(&mut self) -> u32 {
unsafe {
let irq = self.read(GICC_IAR) & 0x1ff;
if irq == 1023 {
panic!("irq_ack: got ID 1023!!!");
}
irq
}
}
unsafe fn irq_eoi(&mut self, irq: u32) {
unsafe {
self.write(GICC_EOIR, irq);
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
+196
View File
@@ -0,0 +1,196 @@
use alloc::vec::Vec;
use core::arch::asm;
use fdt::{node::NodeProperty, Fdt};
use super::{gic::GicDistIf, InterruptController};
use crate::{
dtb::{
get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc},
},
sync::CleanLockToken,
};
use syscall::{
error::{Error, EINVAL},
Result,
};
#[derive(Debug)]
pub struct GicV3 {
pub gic_dist_if: GicDistIf,
pub gic_cpu_if: GicV3CpuIf,
pub gicrs: Vec<(usize, usize)>,
//TODO: GICC, GICH, GICV?
pub irq_range: (usize, usize),
}
impl GicV3 {
pub fn new() -> Self {
GicV3 {
gic_dist_if: GicDistIf::default(),
gic_cpu_if: GicV3CpuIf,
gicrs: Vec::new(),
irq_range: (0, 0),
}
}
pub fn parse(&mut self, fdt: &Fdt) -> Result<()> {
let Some(node) = fdt.find_compatible(&["arm,gic-v3"]) else {
return Err(Error::new(EINVAL));
};
// Clear current registers
//TODO: deinit?
self.gic_dist_if.address = 0;
self.gicrs.clear();
// Get number of GICRs
let gicrs = node
.property("#redistributor-regions")
.and_then(NodeProperty::as_usize)
.unwrap_or(1);
// Read registers
let mut chunks = node.reg().unwrap();
if let Some(gicd) = chunks.next()
&& let Some(addr) = get_mmio_address(fdt, &node, &gicd)
{
unsafe {
self.gic_dist_if.init(crate::PHYS_OFFSET + addr);
}
}
for _ in 0..gicrs {
if let Some(gicr) = chunks.next() {
self.gicrs.push((
get_mmio_address(fdt, &node, &gicr).unwrap(),
gicr.size.unwrap(),
));
}
}
if self.gic_dist_if.address == 0 || self.gicrs.is_empty() {
Err(Error::new(EINVAL))
} else {
Ok(())
}
}
}
impl InterruptHandler for GicV3 {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for GicV3 {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
if let Some(fdt) = fdt_opt {
self.parse(fdt)?;
}
info!("{:X?}", self);
unsafe {
self.gic_cpu_if.init();
}
let idx = *irq_idx;
let cnt = if self.gic_dist_if.nirqs > 1024 {
1024
} else {
self.gic_dist_if.nirqs as usize
};
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("gic irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
Ok(())
}
fn irq_ack(&mut self) -> u32 {
let irq_num = unsafe { self.gic_cpu_if.irq_ack() };
irq_num
}
fn irq_eoi(&mut self, irq_num: u32) {
unsafe { self.gic_cpu_if.irq_eoi(irq_num) }
}
fn irq_enable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_enable(irq_num) }
}
fn irq_disable(&mut self, irq_num: u32) {
unsafe { self.gic_dist_if.irq_disable(irq_num) }
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
let off = match irq_data {
IrqCell::L3(0, irq, _flags) => irq as usize + 32, // SPI
IrqCell::L3(1, irq, _flags) => irq as usize + 16, // PPI
_ => return Err(Error::new(EINVAL)),
};
return Ok(off + self.irq_range.0);
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq >= self.gic_dist_if.nirqs {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
#[derive(Debug)]
pub struct GicV3CpuIf;
impl GicV3CpuIf {
pub unsafe fn init(&mut self) {
unsafe {
// Enable system register access
{
let value = 1_usize;
asm!("msr icc_sre_el1, {}", in(reg) value);
}
// Set control register
{
let value = 0_usize;
asm!("msr icc_ctlr_el1, {}", in(reg) value);
}
// Enable non-secure group 1
{
let value = 1_usize;
asm!("msr icc_igrpen1_el1, {}", in(reg) value);
}
// Set CPU0's Interrupt Priority Mask
{
let value = 0xFF_usize;
asm!("msr icc_pmr_el1, {}", in(reg) value);
}
}
}
unsafe fn irq_ack(&mut self) -> u32 {
unsafe {
let mut irq: usize;
asm!("mrs {}, icc_iar1_el1", out(reg) irq);
irq &= 0x1ff;
if irq == 1023 {
panic!("irq_ack: got ID 1023!!!");
}
irq as u32
}
}
unsafe fn irq_eoi(&mut self, irq: u32) {
unsafe {
asm!("msr icc_eoir1_el1, {}", in(reg) irq as usize);
}
}
}
@@ -0,0 +1,299 @@
use core::ptr::{read_volatile, write_volatile};
use fdt::{node::FdtNode, Fdt};
use super::InterruptController;
use crate::{
dtb::{
get_interrupt, get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc, IRQ_CHIP},
},
sync::CleanLockToken,
};
use syscall::{
error::{Error, EINVAL},
Result,
};
#[inline(always)]
fn ffs(num: u32) -> u32 {
let mut x = num;
if x == 0 {
return 0;
}
let mut r = 1;
if (x & 0xffff) == 0 {
x >>= 16;
r += 16;
}
if (x & 0xff) == 0 {
x >>= 8;
r += 8;
}
if (x & 0xf) == 0 {
x >>= 4;
r += 4;
}
if (x & 0x3) == 0 {
x >>= 2;
r += 2;
}
if (x & 0x1) == 0 {
r += 1;
}
r
}
const PENDING_0: u32 = 0x0;
const PENDING_1: u32 = 0x4;
const PENDING_2: u32 = 0x8;
const ENABLE_0: u32 = 0x18;
const ENABLE_1: u32 = 0x10;
const ENABLE_2: u32 = 0x14;
const DISABLE_0: u32 = 0x24;
const DISABLE_1: u32 = 0x1c;
const DISABLE_2: u32 = 0x20;
pub struct Bcm2835ArmInterruptController {
pub address: usize,
pub irq_range: (usize, usize),
}
impl Bcm2835ArmInterruptController {
pub fn new() -> Self {
Bcm2835ArmInterruptController {
address: 0,
irq_range: (0, 0),
}
}
pub fn parse(fdt: &Fdt) -> Result<(usize, usize, Option<usize>)> {
if let Some(node) = fdt.find_compatible(&["brcm,bcm2836-armctrl-ic"]) {
return unsafe { Bcm2835ArmInterruptController::parse_inner(fdt, &node) };
} else {
return Err(Error::new(EINVAL));
}
}
unsafe fn parse_inner(fdt: &Fdt, node: &FdtNode) -> Result<(usize, usize, Option<usize>)> {
unsafe {
//assert address_cells == 0x1, size_cells == 0x1
let mem = node.reg().unwrap().nth(0).unwrap();
let base = get_mmio_address(fdt, node, &mem).unwrap();
let size = mem.size.unwrap() as u32;
let mut ret_virq = None;
if let Some(interrupt_parent) = node.property("interrupt-parent") {
let phandle = interrupt_parent.as_usize().unwrap() as u32;
let irq = get_interrupt(fdt, node, 0).unwrap();
let ic_idx = IRQ_CHIP.phandle_to_ic_idx(phandle).unwrap();
//PHYS_NONSECURE_PPI only
let virq = IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_xlate(irq)
.unwrap();
info!(
"register bcm2835arm_ctrl as ic_idx {}'s child virq = {}",
ic_idx, virq
);
ret_virq = Some(virq);
}
Ok((base as usize, size as usize, ret_virq))
}
}
unsafe fn init(&mut self) {
unsafe {
debug!("IRQ BCM2835 INIT");
//disable all interrupt
self.write(DISABLE_0, 0xffff_ffff);
self.write(DISABLE_1, 0xffff_ffff);
self.write(DISABLE_2, 0xffff_ffff);
debug!("IRQ BCM2835 END");
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
impl InterruptController for Bcm2835ArmInterruptController {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
let (base, _size, _virq) = match Bcm2835ArmInterruptController::parse(fdt_opt.unwrap()) {
Ok((a, b, c)) => (a, b, c),
Err(_) => return Err(Error::new(EINVAL)),
};
unsafe {
self.address = base + crate::PHYS_OFFSET;
self.init();
let idx = *irq_idx;
let cnt = 3 << 5; //3 * 32 irqs, basic == 8, reg1 = 32, reg2 = 32
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("bcm2835 irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
}
Ok(())
}
fn irq_ack(&mut self) -> u32 {
//TODO: support smp self.read(LOCAL_IRQ_PENDING + 4 * cpu)
let sources = unsafe { self.read(PENDING_0) };
let pending_num = ffs(sources) - 1;
let fast_irq = [
7 + 32,
9 + 32,
10 + 32,
18 + 32,
19 + 32,
21 + 64,
22 + 64,
23 + 64,
24 + 64,
25 + 64,
30 + 64,
];
//fast irq
if pending_num >= 10 && pending_num <= 20 {
return fast_irq[(pending_num - 10) as usize];
}
let pending_num = ffs(sources & 0x3ff) - 1;
match pending_num {
num @ 0..=7 => return num,
8 => {
let sources1 = unsafe { self.read(PENDING_1) };
let irq_0_31 = ffs(sources1) - 1;
return irq_0_31 + 32;
}
9 => {
let sources2 = unsafe { self.read(PENDING_2) };
let irq_32_63 = ffs(sources2) - 1;
return irq_32_63 + 64;
}
num => {
error!(
"unexpected irq pending in BASIC PENDING: 0x{}, sources = 0x{:08x}",
num, sources
);
return num;
}
}
}
fn irq_eoi(&mut self, _irq_num: u32) {}
fn irq_enable(&mut self, irq_num: u32) {
debug!("bcm2835 enable {} {}", irq_num, irq_num & 0x1f);
match irq_num {
num @ 0..=31 => {
let val = 1 << num;
unsafe {
self.write(ENABLE_0, val);
}
}
num @ 32..=63 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(ENABLE_1, val);
}
}
num @ 64..=95 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(ENABLE_2, val);
}
}
_ => return,
}
}
fn irq_disable(&mut self, irq_num: u32) {
match irq_num {
num @ 0..=31 => {
let val = 1 << num;
unsafe {
self.write(DISABLE_0, val);
}
}
num @ 32..=63 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(DISABLE_1, val);
}
}
num @ 64..=95 => {
let val = 1 << (num & 0x1f);
unsafe {
self.write(DISABLE_2, val);
}
}
_ => return,
}
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
//assert interrupt-cells == 0x2
match irq_data {
IrqCell::L2(bank, irq) => {
//TODO: check bank && irq
let hwirq = (bank as usize) << 5 | (irq as usize);
let off = hwirq + self.irq_range.0;
Ok(off)
}
_ => Err(Error::new(EINVAL)),
}
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq > 95 {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
impl InterruptHandler for Bcm2835ArmInterruptController {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {
unsafe {
let irq = self.irq_ack();
if let Some(virq) = self.irq_to_virq(irq)
&& virq < 1024
{
if let Some(handler) = &mut IRQ_CHIP.irq_desc[virq].handler {
handler.irq_handler(virq as u32, token);
}
} else {
error!("unexpected irq num {}", irq);
}
self.irq_eoi(irq);
}
}
}
@@ -0,0 +1,231 @@
use super::InterruptController;
use crate::{
arch::device::{ROOT_IC_IDX, ROOT_IC_IDX_IS_SET},
dtb::{
get_mmio_address,
irqchip::{InterruptHandler, IrqCell, IrqDesc},
},
sync::CleanLockToken,
};
use core::{
arch::asm,
ptr::{read_volatile, write_volatile},
sync::atomic::Ordering,
};
use fdt::{node::FdtNode, Fdt};
use syscall::{
error::{Error, EINVAL},
Result,
};
const LOCAL_CONTROL: u32 = 0x000;
const LOCAL_PRESCALER: u32 = 0x008;
const LOCAL_GPU_ROUTING: u32 = 0x00C;
const LOCAL_TIMER_INT_CONTROL0: u32 = 0x040;
const LOCAL_IRQ_PENDING: u32 = 0x060;
const LOCAL_IRQ_CNTPNSIRQ: u32 = 0x1;
const LOCAL_IRQ_GPU_FAST: u32 = 0x8;
const LOCAL_IRQ_PMU_FAST: u32 = 0x9;
const LOCAL_IRQ_LAST: u32 = LOCAL_IRQ_PMU_FAST;
#[inline(always)]
fn ffs(num: u32) -> u32 {
let mut x = num;
if x == 0 {
return 0;
}
let mut r = 1;
if (x & 0xffff) == 0 {
x >>= 16;
r += 16;
}
if (x & 0xff) == 0 {
x >>= 8;
r += 8;
}
if (x & 0xf) == 0 {
x >>= 4;
r += 4;
}
if (x & 0x3) == 0 {
x >>= 2;
r += 2;
}
if (x & 0x1) == 0 {
r += 1;
}
r
}
pub struct Bcm2836ArmInterruptController {
pub address: usize,
pub irq_range: (usize, usize),
pub active_cpu: u32,
}
impl Bcm2836ArmInterruptController {
pub fn new() -> Self {
Bcm2836ArmInterruptController {
address: 0,
irq_range: (0, 0),
active_cpu: 0,
}
}
pub fn parse(fdt: &Fdt) -> Result<(usize, usize)> {
if let Some(node) = fdt.find_compatible(&["brcm,bcm2836-l1-intc"]) {
return Bcm2836ArmInterruptController::parse_inner(fdt, &node);
} else {
return Err(Error::new(EINVAL));
}
}
fn parse_inner(fdt: &Fdt, node: &FdtNode) -> Result<(usize, usize)> {
//assert address_cells == 0x1, size_cells == 0x1
let reg = node.reg().unwrap().nth(0).unwrap();
let addr = get_mmio_address(fdt, node, &reg).unwrap();
Ok((addr, reg.size.unwrap()))
}
unsafe fn init(&mut self) {
unsafe {
debug!("IRQ BCM2836 INIT");
//init local timer freq
self.write(LOCAL_CONTROL, 0x0);
self.write(LOCAL_PRESCALER, 0x8000_0000);
//routing all irq to core
self.write(LOCAL_GPU_ROUTING, self.active_cpu);
debug!("routing all irq to core {}", self.active_cpu);
debug!("IRQ BCM2836 END");
}
}
unsafe fn read(&self, reg: u32) -> u32 {
unsafe {
let val = read_volatile((self.address + reg as usize) as *const u32);
val
}
}
unsafe fn write(&mut self, reg: u32, value: u32) {
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
}
impl InterruptHandler for Bcm2836ArmInterruptController {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for Bcm2836ArmInterruptController {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> Result<()> {
let (base, _size) = match Bcm2836ArmInterruptController::parse(fdt_opt.unwrap()) {
Ok((a, b)) => (a, b),
Err(_) => return Err(Error::new(EINVAL)),
};
unsafe {
self.address = base + crate::PHYS_OFFSET;
let cpuid: usize;
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
self.active_cpu = cpuid as u32 & 0x3;
self.init();
let idx = *irq_idx;
let cnt = LOCAL_IRQ_LAST as usize;
let mut i: usize = 0;
//only support linear irq map now.
while i < cnt && (idx + i < 1024) {
irq_desc[idx + i].basic.ic_idx = ic_idx;
irq_desc[idx + i].basic.ic_irq = i as u32;
irq_desc[idx + i].basic.used = true;
i += 1;
}
info!("bcm2836 irq_range = ({}, {})", idx, idx + cnt);
self.irq_range = (idx, idx + cnt);
*irq_idx = idx + cnt;
}
//raspi 3b+ dts doesn't follow the rule to set root parent interrupt controller
//so we should set it manually.
ROOT_IC_IDX.store(ic_idx, Ordering::Relaxed);
ROOT_IC_IDX_IS_SET.store(1, Ordering::Relaxed);
Ok(())
}
fn irq_ack(&mut self) -> u32 {
let cpuid: usize;
unsafe {
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
}
let cpu = cpuid as u32 & 0x3;
let sources: u32 = unsafe { self.read(LOCAL_IRQ_PENDING + 4 * cpu) };
ffs(sources) - 1
}
fn irq_eoi(&mut self, _irq_num: u32) {}
fn irq_enable(&mut self, irq_num: u32) {
debug!("bcm2836 enable {}", irq_num);
match irq_num {
LOCAL_IRQ_CNTPNSIRQ => unsafe {
let cpuid: usize;
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
let cpu = cpuid as u32 & 0x3;
let mut reg_val = self.read(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu);
reg_val |= 0x2;
self.write(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu, reg_val);
},
LOCAL_IRQ_GPU_FAST => {
//GPU IRQ always enable
}
_ => {
//ignore
}
}
}
fn irq_disable(&mut self, irq_num: u32) {
match irq_num {
LOCAL_IRQ_CNTPNSIRQ => unsafe {
let cpuid: usize;
asm!("mrs {}, mpidr_el1", out(reg) cpuid);
let cpu = cpuid as u32 & 0x3;
let mut reg_val = self.read(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu);
reg_val &= !0x2;
self.write(LOCAL_TIMER_INT_CONTROL0 + 4 * cpu, reg_val);
},
LOCAL_IRQ_GPU_FAST => {
//GPU IRQ always enable
}
_ => {
//ignore
}
}
}
fn irq_xlate(&self, irq_data: IrqCell) -> Result<usize> {
//assert interrupt-cells == 0x2
match irq_data {
IrqCell::L2(irq, _) => Ok(irq as usize + self.irq_range.0),
_ => Err(Error::new(EINVAL)),
}
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq > LOCAL_IRQ_LAST {
None
} else {
Some(self.irq_range.0 + hwirq as usize)
}
}
}
+41
View File
@@ -0,0 +1,41 @@
use crate::dtb::irqchip::{InterruptController, IRQ_CHIP};
use alloc::boxed::Box;
use fdt::{node::FdtNode, Fdt};
pub(crate) mod gic;
pub(crate) mod gicv3;
mod irq_bcm2835;
mod irq_bcm2836;
mod null;
pub(crate) fn new_irqchip(ic_str: &str) -> Option<Box<dyn InterruptController>> {
if ic_str.contains("arm,gic-v3") {
Some(Box::new(gicv3::GicV3::new()))
} else if ic_str.contains("arm,cortex-a15-gic") || ic_str.contains("arm,gic-400") {
Some(Box::new(gic::GenericInterruptController::new()))
} else if ic_str.contains("brcm,bcm2836-l1-intc") {
Some(Box::new(irq_bcm2836::Bcm2836ArmInterruptController::new()))
} else if ic_str.contains("brcm,bcm2836-armctrl-ic") {
Some(Box::new(irq_bcm2835::Bcm2835ArmInterruptController::new()))
} else {
warn!("no driver for interrupt controller {:?}", ic_str);
//TODO: return None and handle it properly
Some(Box::new(null::Null))
}
}
pub(crate) fn ic_for_chip(fdt: &Fdt, node: &FdtNode) -> Option<usize> {
if let Some(_) = node.property("interrupts-extended") {
error!("multi-parented device not supported");
None
} else if let Some(irqc_phandle) = node
.property("interrupt-parent")
.or(fdt.root().property("interrupt-parent"))
.and_then(|f| f.as_usize())
{
unsafe { IRQ_CHIP.phandle_to_ic_idx(irqc_phandle as u32) }
} else {
error!("no irq parent found");
None
}
}
+41
View File
@@ -0,0 +1,41 @@
use fdt::Fdt;
use syscall::{
error::{Error, EINVAL},
Result,
};
use super::InterruptController;
use crate::{
dtb::irqchip::{InterruptHandler, IrqCell, IrqDesc},
sync::CleanLockToken,
};
pub struct Null;
impl InterruptHandler for Null {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {}
}
impl InterruptController for Null {
fn irq_init(
&mut self,
_fdt_opt: Option<&Fdt>,
_irq_desc: &mut [IrqDesc; 1024],
_ic_idx: usize,
_irq_idx: &mut usize,
) -> Result<()> {
Ok(())
}
fn irq_ack(&mut self) -> u32 {
unimplemented!()
}
fn irq_eoi(&mut self, _irq_num: u32) {}
fn irq_enable(&mut self, _irq_num: u32) {}
fn irq_disable(&mut self, _irq_num: u32) {}
fn irq_xlate(&self, _irq_data: IrqCell) -> Result<usize> {
Err(Error::new(EINVAL))
}
fn irq_to_virq(&self, _hwirq: u32) -> Option<usize> {
None
}
}
+60
View File
@@ -0,0 +1,60 @@
use crate::info;
use core::sync::atomic::{AtomicUsize, Ordering};
use fdt::Fdt;
pub mod cpu;
pub mod generic_timer;
pub mod irqchip;
pub mod rtc;
pub mod serial;
use crate::dtb::irqchip::IRQ_CHIP;
use irqchip::ic_for_chip;
pub static ROOT_IC_IDX: AtomicUsize = AtomicUsize::new(0);
pub static ROOT_IC_IDX_IS_SET: AtomicUsize = AtomicUsize::new(0);
unsafe fn init_root_ic(fdt: &Fdt) {
unsafe {
let is_set = ROOT_IC_IDX_IS_SET.load(Ordering::Relaxed);
if is_set != 0 {
let ic_idx = ROOT_IC_IDX.load(Ordering::Relaxed);
info!("Already selected {} as root ic", ic_idx);
return;
}
let root_irqc_phandle = fdt
.root()
.property("interrupt-parent")
.unwrap()
.as_usize()
.unwrap();
let ic_idx = IRQ_CHIP
.phandle_to_ic_idx(root_irqc_phandle as u32)
.unwrap();
info!("select {} as root ic", ic_idx);
ROOT_IC_IDX.store(ic_idx, Ordering::Relaxed);
}
}
pub unsafe fn init_devicetree(fdt: &Fdt) {
unsafe {
info!("IRQCHIP INIT");
crate::dtb::irqchip::init(&fdt);
init_root_ic(&fdt);
info!("GIT INIT");
generic_timer::init(fdt);
info!("SERIAL INIT");
serial::init(fdt);
info!("RTC INIT");
rtc::init(fdt);
}
}
pub struct ArchPercpuMisc;
impl ArchPercpuMisc {
pub const fn default() -> Self {
Self
}
}
+41
View File
@@ -0,0 +1,41 @@
use crate::{dtb::get_mmio_address, sync::CleanLockToken, time};
use core::ptr::read_volatile;
static RTC_DR: usize = 0x000;
pub unsafe fn init(fdt: &fdt::Fdt) {
if let Some(node) = fdt.find_compatible(&["arm,pl031"]) {
match node
.reg()
.and_then(|mut iter| iter.next())
.and_then(|region| get_mmio_address(fdt, &node, &region))
{
Some(phys) => {
let mut rtc = Pl031rtc { phys };
info!("PL031 RTC at {:#x}", rtc.phys);
let mut token = unsafe { CleanLockToken::new() };
*time::START.lock(token.token()) = (rtc.time() as u128) * time::NANOS_PER_SEC;
}
None => {
warn!("No PL031 RTC registers");
}
}
} else {
warn!("No PL031 RTC found");
}
}
struct Pl031rtc {
pub phys: usize,
}
impl Pl031rtc {
unsafe fn read(&self, reg: usize) -> u32 {
unsafe { read_volatile((crate::PHYS_OFFSET + self.phys + reg) as *const u32) }
}
pub fn time(&mut self) -> u64 {
let seconds = unsafe { self.read(RTC_DR) } as u64;
seconds
}
}
+59
View File
@@ -0,0 +1,59 @@
use alloc::boxed::Box;
use fdt::Fdt;
pub use crate::dtb::serial::COM1;
use crate::{
arch::device::irqchip::ic_for_chip,
dtb::{
get_interrupt,
irqchip::{register_irq, InterruptHandler, IRQ_CHIP},
},
scheme::irq::irq_trigger,
sync::CleanLockToken,
};
pub struct Com1Irq {}
impl InterruptHandler for Com1Irq {
fn irq_handler(&mut self, irq: u32, token: &mut CleanLockToken) {
COM1.lock().receive(token);
unsafe {
// FIXME add_irq accepts a u8 as irq number
// PercpuBlock::current().stats.add_irq(irq);
irq_trigger(irq.try_into().unwrap(), token);
IRQ_CHIP.irq_eoi(irq);
}
}
}
pub unsafe fn init(fdt: &Fdt) {
unsafe {
//TODO: find actual serial device, not just any PL011
if let Some(node) = fdt.find_compatible(&["arm,pl011"]) {
let irq = get_interrupt(fdt, &node, 0).unwrap();
if let Some(ic_idx) = ic_for_chip(&fdt, &node) {
let virq = IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_xlate(irq)
.unwrap();
info!("serial_port virq = {}", virq);
register_irq(virq as u32, Box::new(Com1Irq {}));
IRQ_CHIP.irq_enable(virq as u32);
} else {
error!("serial port irq parent not found");
}
}
COM1.lock().enable_irq();
}
}
pub unsafe fn init_acpi(irq: u32) {
unsafe {
//TODO: what should chip index be?
let virq = IRQ_CHIP.irq_chip_list.chips[0].ic.irq_to_virq(irq).unwrap();
info!("serial_port virq = {}", virq);
register_irq(virq as u32, Box::new(Com1Irq {}));
IRQ_CHIP.irq_enable(virq as u32);
COM1.lock().enable_irq();
}
}
+236
View File
@@ -0,0 +1,236 @@
use ::syscall::Exception;
use rmm::VirtualAddress;
use crate::{
context::signal::excp_handler,
exception_stack,
memory::{ArchIntCtx, GenericPfFlags},
sync::CleanLockToken,
syscall,
};
use super::InterruptStack;
exception_stack!(synchronous_exception_at_el1_with_sp0, |stack| {
println!("Synchronous exception at EL1 with SP0");
stack.trace();
loop {}
});
fn exception_code(esr: usize) -> u8 {
((esr >> 26) & 0x3f) as u8
}
fn iss(esr: usize) -> u32 {
(esr & 0x01ff_ffff) as u32
}
unsafe fn far_el1() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, far_el1", out(reg) ret);
ret
}
}
unsafe fn instr_data_abort_inner(
stack: &mut InterruptStack,
from_user: bool,
instr_not_data: bool,
_from: &str,
) -> bool {
unsafe {
let iss = iss(stack.iret.esr_el1);
let fsc = iss & 0x3F;
//dbg!(fsc);
let was_translation_fault = fsc >= 0b000100 && fsc <= 0b000111;
//let was_permission_fault = fsc >= 0b001101 && fsc <= 0b001111;
let write_not_read_if_data = iss & (1 << 6) != 0;
let mut flags = GenericPfFlags::empty();
flags.set(GenericPfFlags::PRESENT, !was_translation_fault);
// TODO: RMW instructions may "involve" writing to (possibly invalid) memory, but AArch64
// doesn't appear to require that flag to be set if the read alone would trigger a fault.
flags.set(
GenericPfFlags::INVOLVED_WRITE,
write_not_read_if_data && !instr_not_data,
);
flags.set(GenericPfFlags::INSTR_NOT_DATA, instr_not_data);
flags.set(GenericPfFlags::USER_NOT_SUPERVISOR, from_user);
let faulting_addr = VirtualAddress::new(far_el1());
//dbg!(faulting_addr, flags, from);
crate::memory::page_fault_handler(stack, flags, faulting_addr).is_ok()
}
}
unsafe fn cntfrq_el0() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, cntfrq_el0", out(reg) ret);
ret
}
}
unsafe fn cntpct_el0() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, cntpct_el0", out(reg) ret);
ret
}
}
unsafe fn cntvct_el0() -> usize {
unsafe {
let ret: usize;
core::arch::asm!("mrs {}, cntvct_el0", out(reg) ret);
ret
}
}
unsafe fn instr_trapped_msr_mrs_inner(
stack: &mut InterruptStack,
_from_user: bool,
_instr_not_data: bool,
_from: &str,
) -> bool {
unsafe {
let iss = iss(stack.iret.esr_el1);
// let res0 = (iss & 0x1C0_0000) >> 22;
let op0 = (iss & 0x030_0000) >> 20;
let op2 = (iss & 0x00e_0000) >> 17;
let op1 = (iss & 0x001_c000) >> 14;
let crn = (iss & 0x000_3c00) >> 10;
let rt = (iss & 0x000_03e0) >> 5;
let crm = (iss & 0x000_001e) >> 1;
let dir = iss & 0x000_0001;
/*
print!("iss=0x{:x}, res0=0b{:03b}, op0=0b{:02b}\n
op2=0b{:03b}, op1=0b{:03b}, crn=0b{:04b}\n
rt=0b{:05b}, crm=0b{:04b}, dir=0b{:b}\n",
iss, res0, op0, op2, op1, crn, rt, crm, dir);
*/
match (op0, op1, crn, crm, op2, dir) {
//MRS <Xt>, CNTFRQ_EL0
(0b11, 0b011, 0b1110, 0b0000, 0b000, 0b1) => {
let reg_val = cntfrq_el0();
stack.store_reg(rt as usize, reg_val);
//skip faulting instruction, A64 instructions are always 32-bits
stack.iret.elr_el1 += 4;
return true;
}
//MRS <Xt>, CNTPCT_EL0
(0b11, 0b011, 0b1110, 0b0000, 0b001, 0b1) => {
let reg_val = cntpct_el0();
stack.store_reg(rt as usize, reg_val);
//skip faulting instruction, A64 instructions are always 32-bits
stack.iret.elr_el1 += 4;
return true;
}
//MRS <Xt>, CNTVCT_EL0
(0b11, 0b011, 0b1110, 0b0000, 0b010, 0b1) => {
let reg_val = cntvct_el0();
stack.store_reg(rt as usize, reg_val);
//skip faulting instruction, A64 instructions are always 32-bits
stack.iret.elr_el1 += 4;
return true;
}
_ => {}
}
false
}
}
exception_stack!(synchronous_exception_at_el1_with_spx, |stack| {
unsafe {
if !pf_inner(
stack,
exception_code(stack.iret.esr_el1),
"sync_exc_el1_spx",
) {
println!("Synchronous exception at EL1 with SPx");
if exception_code(stack.iret.esr_el1) == 0b100101 {
let far_el1 = far_el1();
println!("FAR_EL1 = 0x{:08x}", far_el1);
} else if exception_code(stack.iret.esr_el1) == 0b100100 {
let far_el1 = far_el1();
println!("USER FAR_EL1 = 0x{:08x}", far_el1);
}
stack.trace();
loop {}
}
}
});
unsafe fn pf_inner(stack: &mut InterruptStack, ty: u8, from: &str) -> bool {
unsafe {
match ty {
// "Data Abort taken from a lower Exception level"
0b100100 => instr_data_abort_inner(stack, true, false, from),
// "Data Abort taken without a change in Exception level"
0b100101 => instr_data_abort_inner(stack, false, false, from),
// "Instruction Abort taken from a lower Exception level"
0b100000 => instr_data_abort_inner(stack, true, true, from),
// "Instruction Abort taken without a change in Exception level"
0b100001 => instr_data_abort_inner(stack, false, true, from),
// "Trapped MSR, MRS or System instruction execution in AArch64 state"
0b011000 => instr_trapped_msr_mrs_inner(stack, true, true, from),
_ => return false,
}
}
}
exception_stack!(synchronous_exception_at_el0, |stack| {
unsafe {
match exception_code(stack.iret.esr_el1) {
0b010101 => {
let scratch = &stack.scratch;
let mut token = CleanLockToken::new();
let ret = syscall::syscall(
scratch.x8, scratch.x0, scratch.x1, scratch.x2, scratch.x3, scratch.x4,
scratch.x5, &mut token,
);
stack.scratch.x0 = ret;
}
ty => {
if !pf_inner(stack, ty as u8, "sync_exc_el0") {
error!(
"FATAL: Not an SVC induced synchronous exception (ty={:b})",
ty
);
println!("FAR_EL1: {:#0x}", far_el1());
//crate::debugger::debugger(None);
stack.trace();
excp_handler(Exception {
kind: 0, // TODO
});
}
}
}
}
});
exception_stack!(unhandled_exception, |stack| {
println!("Unhandled exception");
stack.trace();
loop {}
});
impl ArchIntCtx for InterruptStack {
fn ip(&self) -> usize {
self.iret.elr_el1
}
fn recover_and_efault(&mut self) {
// Set the return value to nonzero to indicate usercopy failure (EFAULT), and emulate the
// return instruction by setting the return pointer to the saved LR value.
self.iret.elr_el1 = self.preserved.x30;
self.scratch.x0 = 1;
}
}
+420
View File
@@ -0,0 +1,420 @@
use crate::{panic, syscall::IntRegisters};
#[derive(Default)]
#[repr(C, packed)]
pub struct ScratchRegisters {
pub x0: usize,
pub x1: usize,
pub x2: usize,
pub x3: usize,
pub x4: usize,
pub x5: usize,
pub x6: usize,
pub x7: usize,
pub x8: usize,
pub x9: usize,
pub x10: usize,
pub x11: usize,
pub x12: usize,
pub x13: usize,
pub x14: usize,
pub x15: usize,
pub x16: usize,
pub x17: usize,
pub x18: usize,
pub _padding: usize,
}
impl ScratchRegisters {
pub fn dump(&self) {
println!("X0: {:>016X}", { self.x0 });
println!("X1: {:>016X}", { self.x1 });
println!("X2: {:>016X}", { self.x2 });
println!("X3: {:>016X}", { self.x3 });
println!("X4: {:>016X}", { self.x4 });
println!("X5: {:>016X}", { self.x5 });
println!("X6: {:>016X}", { self.x6 });
println!("X7: {:>016X}", { self.x7 });
println!("X8: {:>016X}", { self.x8 });
println!("X9: {:>016X}", { self.x9 });
println!("X10: {:>016X}", { self.x10 });
println!("X11: {:>016X}", { self.x11 });
println!("X12: {:>016X}", { self.x12 });
println!("X13: {:>016X}", { self.x13 });
println!("X14: {:>016X}", { self.x14 });
println!("X15: {:>016X}", { self.x15 });
println!("X16: {:>016X}", { self.x16 });
println!("X17: {:>016X}", { self.x17 });
println!("X18: {:>016X}", { self.x18 });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct PreservedRegisters {
//TODO: is X30 a preserved register?
pub x19: usize,
pub x20: usize,
pub x21: usize,
pub x22: usize,
pub x23: usize,
pub x24: usize,
pub x25: usize,
pub x26: usize,
pub x27: usize,
pub x28: usize,
pub x29: usize,
pub x30: usize,
}
impl PreservedRegisters {
pub fn dump(&self) {
println!("X19: {:>016X}", { self.x19 });
println!("X20: {:>016X}", { self.x20 });
println!("X21: {:>016X}", { self.x21 });
println!("X22: {:>016X}", { self.x22 });
println!("X23: {:>016X}", { self.x23 });
println!("X24: {:>016X}", { self.x24 });
println!("X25: {:>016X}", { self.x25 });
println!("X26: {:>016X}", { self.x26 });
println!("X27: {:>016X}", { self.x27 });
println!("X28: {:>016X}", { self.x28 });
println!("X29: {:>016X}", { self.x29 });
println!("X30: {:>016X}", { self.x30 });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct IretRegisters {
// occurred
// The exception vector disambiguates at which EL the interrupt
pub sp_el0: usize, // Shouldn't be used if interrupt occurred at EL1
pub esr_el1: usize,
pub spsr_el1: usize,
pub elr_el1: usize,
}
impl IretRegisters {
pub fn dump(&self) {
println!("ELR_EL1: {:>016X}", { self.elr_el1 });
println!("SPSR_EL1: {:>016X}", { self.spsr_el1 });
println!("ESR_EL1: {:>016X}", { self.esr_el1 });
println!("SP_EL0: {:>016X}", { self.sp_el0 });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct InterruptStack {
pub iret: IretRegisters,
pub scratch: ScratchRegisters,
pub preserved: PreservedRegisters,
}
impl InterruptStack {
pub fn init(&mut self) {}
pub fn frame_pointer(&self) -> usize {
self.preserved.x29
}
pub fn stack_pointer(&self) -> usize {
self.iret.sp_el0
}
pub fn set_stack_pointer(&mut self, sp: usize) {
self.iret.sp_el0 = sp;
}
pub fn sig_archdep_reg(&self) -> usize {
self.scratch.x0
}
pub fn set_instr_pointer(&mut self, ip: usize) {
self.iret.elr_el1 = ip;
}
pub fn instr_pointer(&self) -> usize {
self.iret.elr_el1
}
pub fn set_arg1(&mut self, arg_opt: Option<usize>) {
if let Some(arg) = arg_opt {
self.scratch.x1 = arg;
}
}
pub fn dump(&self) {
self.iret.dump();
self.scratch.dump();
self.preserved.dump();
}
pub fn trace(&self) {
self.dump();
unsafe {
panic::user_stack_trace(&self);
panic::stack_trace();
}
}
/// Saves all registers to a struct used by the proc:
/// scheme to read/write registers.
pub fn save(&self, all: &mut IntRegisters) {
/*TODO: aarch64 registers
all.elr_el1 = self.iret.elr_el1;
all.spsr_el1 = self.iret.spsr_el1;
all.esr_el1 = self.iret.esr_el1;
all.sp_el0 = self.iret.sp_el0;
all.padding = 0;
*/
all.x30 = self.preserved.x30;
all.x29 = self.preserved.x29;
all.x28 = self.preserved.x28;
all.x27 = self.preserved.x27;
all.x26 = self.preserved.x26;
all.x25 = self.preserved.x25;
all.x24 = self.preserved.x24;
all.x23 = self.preserved.x23;
all.x22 = self.preserved.x22;
all.x21 = self.preserved.x21;
all.x20 = self.preserved.x20;
all.x19 = self.preserved.x19;
all.x18 = self.scratch.x18;
all.x17 = self.scratch.x17;
all.x16 = self.scratch.x16;
all.x15 = self.scratch.x15;
all.x14 = self.scratch.x14;
all.x13 = self.scratch.x13;
all.x12 = self.scratch.x12;
all.x11 = self.scratch.x11;
all.x10 = self.scratch.x10;
all.x9 = self.scratch.x9;
all.x8 = self.scratch.x8;
all.x7 = self.scratch.x7;
all.x6 = self.scratch.x6;
all.x5 = self.scratch.x5;
all.x4 = self.scratch.x4;
all.x3 = self.scratch.x3;
all.x2 = self.scratch.x2;
all.x1 = self.scratch.x1;
all.x0 = self.scratch.x0;
}
/// Loads all registers from a struct used by the proc:
/// scheme to read/write registers.
pub fn load(&mut self, all: &IntRegisters) {
/*TODO: aarch64 registers
self.iret.elr_el1 = all.elr_el1;
self.iret.spsr_el1 = all.spsr_el1;
self.iret.esr_el1 = all.esr_el1;
self.iret.sp_el0 = all.sp_el0;
*/
self.preserved.x30 = all.x30;
self.preserved.x29 = all.x29;
self.preserved.x28 = all.x28;
self.preserved.x27 = all.x27;
self.preserved.x26 = all.x26;
self.preserved.x25 = all.x25;
self.preserved.x24 = all.x24;
self.preserved.x23 = all.x23;
self.preserved.x22 = all.x22;
self.preserved.x21 = all.x21;
self.preserved.x20 = all.x20;
self.preserved.x19 = all.x19;
self.scratch.x18 = all.x18;
self.scratch.x17 = all.x17;
self.scratch.x16 = all.x16;
self.scratch.x15 = all.x15;
self.scratch.x14 = all.x14;
self.scratch.x13 = all.x13;
self.scratch.x12 = all.x12;
self.scratch.x11 = all.x11;
self.scratch.x10 = all.x10;
self.scratch.x9 = all.x9;
self.scratch.x8 = all.x8;
self.scratch.x7 = all.x7;
self.scratch.x6 = all.x6;
self.scratch.x5 = all.x5;
self.scratch.x4 = all.x4;
self.scratch.x3 = all.x3;
self.scratch.x2 = all.x2;
self.scratch.x1 = all.x1;
self.scratch.x0 = all.x0;
}
/// Store a specific generic registers
pub fn store_reg(&mut self, idx: usize, val: usize) {
match idx {
0 => self.scratch.x0 = val,
1 => self.scratch.x1 = val,
2 => self.scratch.x2 = val,
3 => self.scratch.x3 = val,
4 => self.scratch.x4 = val,
5 => self.scratch.x5 = val,
6 => self.scratch.x6 = val,
7 => self.scratch.x7 = val,
8 => self.scratch.x8 = val,
9 => self.scratch.x9 = val,
10 => self.scratch.x10 = val,
11 => self.scratch.x11 = val,
12 => self.scratch.x12 = val,
13 => self.scratch.x13 = val,
14 => self.scratch.x14 = val,
15 => self.scratch.x15 = val,
16 => self.scratch.x16 = val,
17 => self.scratch.x17 = val,
18 => self.scratch.x18 = val,
19 => self.preserved.x19 = val,
20 => self.preserved.x20 = val,
21 => self.preserved.x21 = val,
22 => self.preserved.x22 = val,
23 => self.preserved.x23 = val,
24 => self.preserved.x24 = val,
25 => self.preserved.x25 = val,
26 => self.preserved.x26 = val,
27 => self.preserved.x27 = val,
28 => self.preserved.x28 = val,
29 => self.preserved.x29 = val,
30 => self.preserved.x30 = val,
_ => {}
}
}
//TODO
pub fn set_singlestep(&mut self, _singlestep: bool) {}
}
#[macro_export]
macro_rules! push_scratch {
() => {
"
// Push scratch registers
str x18, [sp, #-16]!
stp x16, x17, [sp, #-16]!
stp x14, x15, [sp, #-16]!
stp x12, x13, [sp, #-16]!
stp x10, x11, [sp, #-16]!
stp x8, x9, [sp, #-16]!
stp x6, x7, [sp, #-16]!
stp x4, x5, [sp, #-16]!
stp x2, x3, [sp, #-16]!
stp x0, x1, [sp, #-16]!
"
};
}
#[macro_export]
macro_rules! pop_scratch {
() => {
"
// Pop scratch registers
ldp x0, x1, [sp], #16
ldp x2, x3, [sp], #16
ldp x4, x5, [sp], #16
ldp x6, x7, [sp], #16
ldp x8, x9, [sp], #16
ldp x10, x11, [sp], #16
ldp x12, x13, [sp], #16
ldp x14, x15, [sp], #16
ldp x16, x17, [sp], #16
ldr x18, [sp], #16
"
};
}
#[macro_export]
macro_rules! push_preserved {
() => {
"
// Push preserved registers
stp x29, x30, [sp, #-16]!
stp x27, x28, [sp, #-16]!
stp x25, x26, [sp, #-16]!
stp x23, x24, [sp, #-16]!
stp x21, x22, [sp, #-16]!
stp x19, x20, [sp, #-16]!
"
};
}
#[macro_export]
macro_rules! pop_preserved {
() => {
"
// Pop preserved registers
ldp x19, x20, [sp], #16
ldp x21, x22, [sp], #16
ldp x23, x24, [sp], #16
ldp x25, x26, [sp], #16
ldp x27, x28, [sp], #16
ldp x29, x30, [sp], #16
"
};
}
#[macro_export]
macro_rules! push_special {
() => {
"
mrs x14, spsr_el1
mrs x15, elr_el1
stp x14, x15, [sp, #-16]!
mrs x14, sp_el0
mrs x15, esr_el1
stp x14, x15, [sp, #-16]!
"
};
}
#[macro_export]
macro_rules! pop_special {
() => {
"
ldp x14, x15, [sp], 16
msr esr_el1, x15
msr sp_el0, x14
ldp x14, x15, [sp], 16
msr elr_el1, x15
msr spsr_el1, x14
"
};
}
#[macro_export]
macro_rules! exception_stack {
($name:ident, |$stack:ident| $code:block) => {
#[unsafe(naked)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(stack: &mut $crate::arch::aarch64::interrupt::InterruptStack) {
unsafe extern "C" fn inner($stack: &mut $crate::arch::aarch64::interrupt::InterruptStack) {
$code
}
core::arch::naked_asm!(
// Backup all userspace registers to stack
push_preserved!(),
push_scratch!(),
push_special!(),
// Call inner function with pointer to stack
"mov x29, sp",
"mov x0, sp",
"bl {}",
// Restore all userspace registers
pop_special!(),
pop_scratch!(),
pop_preserved!(),
"eret",
sym inner,
);
}
};
}
#[unsafe(naked)]
pub unsafe extern "C" fn enter_usermode() -> ! {
core::arch::naked_asm!(
"blr x28",
// Restore all userspace registers
pop_special!(),
pop_scratch!(),
pop_preserved!(),
"eret",
);
}
+56
View File
@@ -0,0 +1,56 @@
use crate::{arch::device::ROOT_IC_IDX, dtb::irqchip::IRQ_CHIP, sync::CleanLockToken};
use core::sync::atomic::Ordering;
// use crate::percpu::PercpuBlock;
unsafe fn irq_ack() -> (u32, Option<usize>) {
unsafe {
let ic = &mut IRQ_CHIP.irq_chip_list.chips[ROOT_IC_IDX.load(Ordering::Relaxed)].ic;
let irq = ic.irq_ack();
(irq, ic.irq_to_virq(irq))
}
}
exception_stack!(irq_at_el0, |_stack| {
unsafe {
let mut token = CleanLockToken::new();
let (irq, virq) = irq_ack();
if let Some(virq) = virq
&& virq < 1024
{
IRQ_CHIP.trigger_virq(virq as u32, &mut token);
} else {
println!("unexpected irq num {}", irq);
}
}
});
exception_stack!(irq_at_el1, |_stack| {
unsafe {
let mut token = CleanLockToken::new();
let (irq, virq) = irq_ack();
if let Some(virq) = virq
&& virq < 1024
{
IRQ_CHIP.trigger_virq(virq as u32, &mut token);
} else {
println!("unexpected irq num {}", irq);
}
}
});
/*
pub unsafe fn irq_handler_gentimer(irq: u32) {
GENTIMER.clear_irq();
{
*time::OFFSET.lock() += GENTIMER.clk_freq as u128;
}
timeout::trigger();
context::switch::tick();
trigger(irq);
GENTIMER.reload_count();
}
*/
+49
View File
@@ -0,0 +1,49 @@
//! Interrupt instructions
use core::arch::asm;
#[macro_use]
pub mod handler;
pub mod exception;
pub mod irq;
pub mod syscall;
pub mod trace;
pub use self::handler::InterruptStack;
/// Clear interrupts
#[inline(always)]
pub unsafe fn disable() {
unsafe {
asm!("msr daifset, #2");
}
}
/// Set interrupts and halt
/// This will atomically wait for the next interrupt
/// Performing enable followed by halt is not guaranteed to be atomic, use this instead!
#[inline(always)]
pub unsafe fn enable_and_halt() {
unsafe {
asm!("wfi", "msr daifclr, #2", "nop");
}
}
/// Set interrupts and nop
/// This will enable interrupts and allow the IF flag to be processed
/// Simply enabling interrupts does not gurantee that they will trigger, use this instead!
#[inline(always)]
pub unsafe fn enable_and_nop() {
unsafe {
asm!("msr daifclr, #2", "nop");
}
}
/// Halt instruction
#[inline(always)]
pub unsafe fn halt() {
unsafe {
asm!("wfi");
}
}
+49
View File
@@ -0,0 +1,49 @@
#[unsafe(no_mangle)]
pub unsafe extern "C" fn do_exception_unhandled() {}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn do_exception_synchronous() {}
#[allow(dead_code)]
#[repr(C, packed)]
pub struct SyscallStack {
pub elr_el1: usize,
pub padding: usize,
pub tpidr: usize,
pub tpidrro: usize,
pub rflags: usize,
pub esr: usize,
pub sp: usize,
pub lr: usize,
pub fp: usize,
pub x28: usize,
pub x27: usize,
pub x26: usize,
pub x25: usize,
pub x24: usize,
pub x23: usize,
pub x22: usize,
pub x21: usize,
pub x20: usize,
pub x19: usize,
pub x18: usize,
pub x17: usize,
pub x16: usize,
pub x15: usize,
pub x14: usize,
pub x13: usize,
pub x12: usize,
pub x11: usize,
pub x10: usize,
pub x9: usize,
pub x8: usize,
pub x7: usize,
pub x6: usize,
pub x5: usize,
pub x4: usize,
pub x3: usize,
pub x2: usize,
pub x1: usize,
pub x0: usize,
}
pub use super::handler::enter_usermode;
+32
View File
@@ -0,0 +1,32 @@
use core::arch::asm;
pub struct StackTrace {
pub fp: usize,
pub pc_ptr: *const usize,
}
impl StackTrace {
#[inline(always)]
pub unsafe fn start() -> Option<Self> {
unsafe {
let fp: usize;
asm!("mov {}, fp", out(reg) fp);
let pc_ptr = fp.checked_add(size_of::<usize>())?;
Some(StackTrace {
fp,
pc_ptr: pc_ptr as *const usize,
})
}
}
pub unsafe fn next(self) -> Option<Self> {
unsafe {
let fp = *(self.fp as *const usize);
let pc_ptr = fp.checked_add(size_of::<usize>())?;
Some(StackTrace {
fp: fp,
pc_ptr: pc_ptr as *const usize,
})
}
}
}
+30
View File
@@ -0,0 +1,30 @@
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum IpiKind {
Wakeup = 0x40,
Tlb = 0x41,
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum IpiTarget {
Other = 3,
}
#[inline(always)]
pub fn ipi(_kind: IpiKind, _target: IpiTarget) {
if cfg!(not(feature = "multi_core")) {
return;
}
// FIXME implement
}
#[inline(always)]
pub fn ipi_single(_kind: IpiKind, _target: &crate::percpu::PercpuBlock) {
if cfg!(not(feature = "multi_core")) {
return;
}
// FIXME implement
}
+23
View File
@@ -0,0 +1,23 @@
use crate::{
cpu_set::LogicalCpuId,
memory::{RmmA, RmmArch},
percpu::PercpuBlock,
};
impl PercpuBlock {
pub fn current() -> &'static Self {
unsafe { &*(crate::arch::device::cpu::registers::control_regs::tpidr_el1() as *const Self) }
}
}
#[cold]
pub unsafe fn init(cpu_id: LogicalCpuId) {
unsafe {
let frame = crate::memory::allocate_frame().expect("failed to allocate percpu memory");
let virt = RmmA::phys_to_virt(frame.base()).data() as *mut PercpuBlock;
virt.write(PercpuBlock::init(cpu_id));
crate::arch::device::cpu::registers::control_regs::tpidr_el1_write(virt as u64);
}
}
+71
View File
@@ -0,0 +1,71 @@
/// Constants like memory locations
pub mod consts;
/// Debugging support
pub mod debug;
/// Devices
pub mod device;
/// Interrupt instructions
pub mod interrupt;
/// Inter-processor interrupts
pub mod ipi;
/// Miscellaneous
pub mod misc;
/// Paging
pub mod paging;
/// Initialization and start function
pub mod start;
/// Stop function
pub mod stop;
// Interrupt vectors
pub mod vectors;
pub mod time;
pub use ::rmm::aarch64::AArch64Arch as CurrentRmmArch;
pub use arch_copy_to_user as arch_copy_from_user;
#[unsafe(naked)]
pub unsafe extern "C" fn arch_copy_to_user(dst: usize, src: usize, len: usize) -> u8 {
// x0, x1, x2
core::arch::naked_asm!(
"
.global __usercopy_start
__usercopy_start:
mov x4, x0
mov x0, 0
2:
cmp x2, 0
b.eq 3f
ldrb w3, [x1]
strb w3, [x4]
add x4, x4, 1
add x1, x1, 1
sub x2, x2, 1
b 2b
3:
ret
.global __usercopy_end
__usercopy_end:
"
);
}
pub const KFX_SIZE: usize = 1024;
// This function exists as the KFX size is dynamic on x86_64.
pub fn kfx_size() -> usize {
KFX_SIZE
}
+7
View File
@@ -0,0 +1,7 @@
/// Initialize MAIR
#[cold]
pub unsafe fn init() {
unsafe {
rmm::aarch64::init_mair();
}
}
+148
View File
@@ -0,0 +1,148 @@
//! This function is where the kernel sets up IRQ handlers
//! It is incredibly unsafe, and should be minimal in nature
//! It must create the IDT with the correct entries, those entries are
//! defined in other files inside of the `arch` module
use core::{arch::naked_asm, cell::SyncUnsafeCell, slice};
use fdt::Fdt;
use crate::{
allocator,
arch::{device, paging},
devices::graphical_debug,
dtb,
startup::KernelArgs,
};
/// Test of zero values in BSS.
static mut BSS_TEST_ZERO: usize = 0;
/// Test of non-zero values in data.
static mut DATA_TEST_NONZERO: usize = 0xFFFF_FFFF_FFFF_FFFF;
#[repr(C, align(16))]
struct StackAlign<T>(T);
static STACK: SyncUnsafeCell<StackAlign<[u8; 128 * 1024]>> =
SyncUnsafeCell::new(StackAlign([0; 128 * 1024]));
// FIXME use extern "custom"
#[unsafe(naked)]
#[unsafe(no_mangle)]
extern "C" fn kstart() {
naked_asm!("
// BSS should already be zero
adrp x9, {bss_test_zero}
ldr x9, [x9, :lo12:{bss_test_zero}]
cbnz x9, .Lkstart_crash
adrp x9, {data_test_nonzero}
ldr x9, [x9, :lo12:{data_test_nonzero}]
cbz x9, .Lkstart_crash
adrp x1, {stack}
add x1, x1, :lo12:{stack}
mov x2, {stack_size}-16
add sp, x1, x2
// Setup interrupt handlers
ldr x9, =exception_vector_base
msr vbar_el1, x9
mov lr, 0
b {start}
.Lkstart_crash:
mov x9, 0
br x9
",
bss_test_zero = sym BSS_TEST_ZERO,
data_test_nonzero = sym DATA_TEST_NONZERO,
stack = sym STACK,
stack_size = const size_of_val(&STACK),
start = sym start,
);
}
/// The entry to Rust, all things must be initialized
unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! {
unsafe {
let bootstrap = {
let args = args_ptr.read();
// Set up graphical debug
graphical_debug::init(args.env());
// Get hardware descriptor data
//TODO: use env {DTB,RSDT}_{BASE,SIZE}?
let hwdesc_data = if args.hwdesc_base != 0 {
Some(slice::from_raw_parts(
(crate::PHYS_OFFSET + args.hwdesc_base as usize) as *const u8,
args.hwdesc_size as usize,
))
} else {
None
};
let dtb_res = hwdesc_data
.ok_or(fdt::FdtError::BadPtr)
.and_then(|data| Fdt::new(data));
// Try to find serial port prior to logging
if let Ok(dtb) = &dtb_res {
dtb::serial::init_early(dtb);
}
info!("Redox OS starting...");
args.print();
// Initialize RMM
crate::startup::memory::init(&args, None, None);
// Initialize paging
paging::init();
crate::arch::misc::init(crate::cpu_set::LogicalCpuId::new(0));
// Setup kernel heap
allocator::init();
// Activate memory logging
crate::log::init();
// Initialize devices
match dtb_res {
Ok(dtb) => {
dtb::init(hwdesc_data.map(|slice| (slice.as_ptr() as usize, slice.len())));
device::init_devicetree(&dtb);
}
Err(err) => {
dtb::init(None);
warn!("failed to parse DTB: {}", err);
#[cfg(feature = "acpi")]
{
crate::acpi::init(args.acpi_rsdp());
}
}
}
args.bootstrap()
};
crate::startup::kmain(bootstrap);
}
}
#[repr(C, packed)]
#[allow(unused)]
pub struct KernelArgsAp {
cpu_id: u64,
page_table: u64,
stack_start: u64,
stack_end: u64,
}
/// Entry to rust for an AP
#[allow(unused)]
pub unsafe extern "C" fn kstart_ap(_args_ptr: *const KernelArgsAp) -> ! {
loop {}
}
+33
View File
@@ -0,0 +1,33 @@
use crate::sync::CleanLockToken;
use core::arch::asm;
pub unsafe fn kreset() -> ! {
unsafe {
println!("kreset");
asm!("hvc #0",
in("x0") 0x8400_0009_usize,
options(noreturn),
)
}
}
pub unsafe fn emergency_reset() -> ! {
unsafe {
asm!("hvc #0",
in("x0") 0x8400_0009_usize,
options(noreturn),
)
}
}
pub unsafe fn kstop(_token: &mut CleanLockToken) -> ! {
unsafe {
println!("kstop");
asm!("hvc #0",
in("x0") 0x8400_0008_usize,
options(noreturn),
)
}
}
+18
View File
@@ -0,0 +1,18 @@
use crate::{sync::CleanLockToken, time::NANOS_PER_SEC};
pub fn monotonic_absolute(_token: &mut CleanLockToken) -> u128 {
//TODO: aarch64 generic timer counter
let ticks: usize;
unsafe { core::arch::asm!("mrs {}, cntpct_el0", out(reg) ticks) };
let freq: usize;
unsafe { core::arch::asm!("mrs {}, cntfrq_el0", out(reg) freq) };
ticks as u128 * NANOS_PER_SEC / freq as u128
}
pub fn monotonic_resolution() -> u128 {
let freq: usize;
unsafe { core::arch::asm!("mrs {}, cntfrq_el0", out(reg) freq) };
NANOS_PER_SEC / freq as u128
}
+112
View File
@@ -0,0 +1,112 @@
core::arch::global_asm!(
"
// Exception vector stubs
//
// Unhandled exceptions spin in a wfi loop for the moment
// This can be macro-ified
.globl exception_vector_base
.align 11
exception_vector_base:
// Synchronous
.align 7
__vec_00:
b synchronous_exception_at_el1_with_sp0
b __vec_00
// IRQ
.align 7
__vec_01:
b irq_at_el1
b __vec_01
// FIQ
.align 7
__vec_02:
b unhandled_exception
b __vec_02
// SError
.align 7
__vec_03:
b unhandled_exception
b __vec_03
// Synchronous
.align 7
__vec_04:
b synchronous_exception_at_el1_with_spx
b __vec_04
// IRQ
.align 7
__vec_05:
b irq_at_el1
b __vec_05
// FIQ
.align 7
__vec_06:
b unhandled_exception
b __vec_06
// SError
.align 7
__vec_07:
b unhandled_exception
b __vec_07
// Synchronous
.align 7
__vec_08:
b synchronous_exception_at_el0
b __vec_08
// IRQ
.align 7
__vec_09:
b irq_at_el0
b __vec_09
// FIQ
.align 7
__vec_10:
b unhandled_exception
b __vec_10
// SError
.align 7
__vec_11:
b unhandled_exception
b __vec_11
// Synchronous
.align 7
__vec_12:
b unhandled_exception
b __vec_12
// IRQ
.align 7
__vec_13:
b unhandled_exception
b __vec_13
// FIQ
.align 7
__vec_14:
b unhandled_exception
b __vec_14
// SError
.align 7
__vec_15:
b unhandled_exception
b __vec_15
.align 7
exception_vector_end:
"
);
+27
View File
@@ -0,0 +1,27 @@
#[cfg(target_arch = "aarch64")]
#[macro_use]
pub mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use self::aarch64::*;
#[cfg(target_arch = "x86")]
#[macro_use]
pub mod x86;
#[cfg(target_arch = "x86")]
pub use self::x86::*;
#[cfg(target_arch = "x86_64")]
#[macro_use]
pub mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use self::x86_64::*;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[macro_use]
mod x86_shared;
#[cfg(target_arch = "riscv64")]
#[macro_use]
pub mod riscv64;
#[cfg(target_arch = "riscv64")]
pub use self::riscv64::*;
+16
View File
@@ -0,0 +1,16 @@
use super::CurrentRmmArch;
use rmm::Arch;
const PML4_SHIFT: usize = (CurrentRmmArch::PAGE_LEVELS - 1) * CurrentRmmArch::PAGE_ENTRY_SHIFT
+ CurrentRmmArch::PAGE_SHIFT;
/// The size of a single PML4
pub const PML4_SIZE: usize = 1_usize << PML4_SHIFT;
/// Offset to kernel heap
#[inline(always)]
pub fn kernel_heap_offset() -> usize {
crate::kernel_executable_offsets::KERNEL_OFFSET() - PML4_SIZE
}
/// End offset of the user image, i.e. kernel start
pub const USER_END_OFFSET: usize = 1_usize << (CurrentRmmArch::PAGE_ADDRESS_SHIFT - 1);
+19
View File
@@ -0,0 +1,19 @@
use spin::MutexGuard;
use crate::{arch::device::serial::COM1, devices::serial::SerialKind};
pub struct Writer<'a> {
serial: MutexGuard<'a, SerialKind>,
}
impl<'a> Writer<'a> {
pub fn new() -> Writer<'a> {
Writer {
serial: COM1.lock(),
}
}
pub fn write(&mut self, buf: &[u8]) {
self.serial.write(buf);
}
}
+5
View File
@@ -0,0 +1,5 @@
use core::fmt::{Result, Write};
pub fn cpu_info<W: Write>(_w: &mut W) -> Result {
unimplemented!()
}
+42
View File
@@ -0,0 +1,42 @@
use spin::Mutex;
use syscall::{Io, Mmio};
use crate::context::switch::tick;
#[repr(packed(4))]
#[repr(C)]
struct ClintRegs {
/// per-hart MSIP registers
/// bit 0: trigger IPI for the hart
msip: [Mmio<u32>; 4095], // +0000 -- 3fff
_rsrv1: u32,
/// per-hart MTIMECMP registers
/// timer interrupt trigger threshold
mtimecmp: [Mmio<u64>; 4095], // +4000 - bff7
mtime: Mmio<u64> // current time
}
pub struct Clint {
regs: &'static mut ClintRegs,
freq: u64
}
pub static CLINT: Mutex<Option<Clint>> = Mutex::new(None);
impl Clint {
pub fn new(addr: *mut u8, size: usize, freq: usize) -> Self {
assert!(size >= size_of::<ClintRegs>());
Self {
regs: unsafe { (addr as *mut ClintRegs).as_mut().unwrap() },
freq: freq as u64
}
}
pub fn init(self: &mut Self) {
(*self.regs).mtimecmp[0].write((*self.regs).mtime.read() + self.freq / 100);
}
pub fn timer_irq(self: &mut Self, hart: usize) {
(*self.regs).mtimecmp[hart].write((*self.regs).mtimecmp[hart].read() + self.freq / 100);
tick();
}
}
@@ -0,0 +1,150 @@
use crate::{
context,
context::timeout,
dtb::irqchip::{register_irq, InterruptHandler, IrqCell, IRQ_CHIP},
sync::CleanLockToken,
};
use alloc::{boxed::Box, vec::Vec};
use core::{arch::asm, cmp::max};
use fdt::node::FdtNode;
use spin::Mutex;
// This is a Core-Local Interruptor (CLINT). A single device directly routed into each HLIC
// It is responsible for local timer and IPI interrupts
// An example DTS:
// /soc/
// clint@2000000/
// interrupts-extended = <&hlic0 3>, <&hlic0 7>, <&hlic1 3>, <&hlic1 7>,
// <&hlic2 3>, <&hlic2 7>, <&hlic3 3>, <&hlic3 7>;
// reg = <0x200000000 0x10000>;
// compatible = "sifive,clint0", "riscv,clint0";
pub struct Clint {
freq: u64,
next_event: Vec<u64>,
}
pub static CLINT: Mutex<Option<Clint>> = Mutex::new(None);
const TICKS_PER_SECOND: u64 = 100;
const IRQ_IPI: usize = 0;
const IRQ_TIMER: usize = 1;
struct ClintConnector {
hart_id: usize,
irq: usize,
}
impl InterruptHandler for ClintConnector {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {
CLINT
.lock()
.as_mut()
.unwrap()
.irq_handler(self.hart_id, self.irq);
if self.irq == IRQ_TIMER {
// a bit of hack, but it is a really bad idea to call scheduler
// from inside clint irq handler
timeout::trigger(token);
context::switch::tick(token);
}
}
}
fn map_interrupt(irq: u32) -> u32 {
match irq {
3 => 1, // map M-mode IPI to S-mode IPI
7 => 5, // map M-mode timer to S-mode timer
x => x,
}
}
impl Clint {
pub fn new(freq: usize, node: &FdtNode) -> Self {
// TODO IPI
// let reg = clint_node.reg().unwrap().next().unwrap();
// reg.starting_address.add(crate::PHYS_OFFSET) as *mut u8;
// reg.size.unwrap();
let mut me = Self {
freq: freq as u64,
next_event: Vec::new(),
};
let mut interrupts = node
.property("interrupts-extended")
.unwrap()
.value
.as_chunks::<4>()
.0
.iter()
.map(|&x| u32::from_be_bytes(x));
let mut hart_id = 0;
while let Ok([phandle1, irq0, phandle2, irq1]) = interrupts.next_chunk::<4>() {
assert_eq!(
phandle1, phandle2,
"Invalid interrupts-extended property for CLINT"
);
let hlic = unsafe {
IRQ_CHIP
.irq_chip_list
.chips
.iter()
.find(|x| x.phandle == phandle1)
.expect("Couldn't find HLIC in irqchip list for CLINT")
};
// FIXME dirty hack map M-mode interrupts (handled by SBI) to S-mode interrupts we get from SBI
// Why aren't S-mode interrupts in the DTB already?
let irq0 = IrqCell::L1(map_interrupt(irq0));
let irq1 = IrqCell::L1(map_interrupt(irq1));
let virq0 = hlic
.ic
.irq_xlate(irq0)
.expect("Couldn't get virq 0 from HLIC");
let virq1 = hlic
.ic
.irq_xlate(irq1)
.expect("Couldn't get virq 1 from HLIC");
register_irq(virq0 as u32, Box::new(ClintConnector { hart_id, irq: 0 }));
register_irq(virq1 as u32, Box::new(ClintConnector { hart_id, irq: 1 }));
hart_id += 1;
}
me.next_event.resize_with(hart_id, || 0);
me
}
pub(crate) fn irq_handler(self: &mut Self, hart_id: usize, irq: usize) {
match irq {
IRQ_IPI => {
println!("IPI interrupt at {}", hart_id);
}
IRQ_TIMER => {
let mtime: usize;
unsafe {
asm!(
"rdtime t0",
lateout("t0") mtime
)
};
self.next_event[hart_id] =
max(self.next_event[hart_id], mtime as u64) + self.freq / TICKS_PER_SECOND;
sbi_rt::set_timer(self.next_event[hart_id]).expect("SBI timer cannot be set!");
}
_ => {
panic!("Unexpected CLINT irq")
}
}
}
pub fn init(self: &mut Self, hart: usize) {
let mtime: usize;
unsafe {
asm!(
"rdtime t0",
lateout("t0") mtime
)
};
self.next_event[hart] = mtime as u64 + (self.freq / TICKS_PER_SECOND);
sbi_rt::set_timer(self.next_event[hart]).expect("SBI timer cannot be set!");
}
}
+170
View File
@@ -0,0 +1,170 @@
use crate::{
dtb::irqchip::{InterruptController, InterruptHandler, IrqCell, IrqDesc, IRQ_CHIP},
sync::CleanLockToken,
};
use alloc::vec::Vec;
use core::arch::asm;
use fdt::{node::NodeProperty, Fdt};
use syscall::{Error, EINVAL};
// This is a hart-local interrupt controller, a root of irqchip tree
// An example DTS:
// /cpus/
// cpu@1/
// interrupt-controller/
// #interrupt-cells = 0x00000001
// interrupt-controller =
// compatible = "riscv,cpu-intc"
// phandle = 0x00000006
fn acknowledge(interrupt: usize) {
unsafe {
asm!(
"csrc sip, t0",
in("t0") 1usize << interrupt,
options(nostack)
)
}
}
pub unsafe fn interrupt(hart: usize, interrupt: usize, token: &mut CleanLockToken) {
unsafe {
assert!(
hart < CPU_INTERRUPT_HANDLERS.len(),
"Unexpected hart in interrupt routine"
);
acknowledge(interrupt);
let ic_idx = CPU_INTERRUPT_HANDLERS[hart].unwrap_or_else(|| {
panic!(
"No hlic connected to hart {} yet interrupt {} occurred",
hart, interrupt
)
});
let virq = IRQ_CHIP
.irq_to_virq(ic_idx, interrupt as u32)
.unwrap_or_else(|| panic!("HLIC doesn't know of interrupt {}", interrupt));
match &mut IRQ_CHIP.irq_desc[virq].handler {
Some(handler) => {
handler.irq_handler(virq as u32, token);
}
_ => match IRQ_CHIP.irq_desc[virq].basic.child_ic_idx {
Some(ic_idx) => {
IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_handler(virq as u32, token);
}
_ => {
panic!(
"Unconnected interrupt {} occurred on hlic connected to hart {}",
interrupt, hart
);
}
},
}
}
}
pub fn init() {
unsafe {
asm!(
"csrs sie, t0",
in("t0") (0xFFFF),
options(nostack)
)
}
}
static mut CPU_INTERRUPT_HANDLERS: Vec<Option<usize>> = Vec::new();
pub struct Hlic {
virq_base: usize,
}
impl Hlic {
pub(crate) fn new() -> Self {
return Self { virq_base: 0 };
}
}
impl InterruptHandler for Hlic {
fn irq_handler(&mut self, irq: u32, token: &mut CleanLockToken) {
assert!(irq < 16, "Unsupported HLIC interrupt raised!");
unsafe {
IRQ_CHIP.trigger_virq(self.virq_base as u32 + irq, token);
}
}
}
impl InterruptController for Hlic {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> syscall::Result<()> {
let desc = unsafe { &IRQ_CHIP.irq_chip_list.chips[ic_idx] };
let fdt = fdt_opt.unwrap();
let cpu_node = fdt
.find_all_nodes("/cpus/cpu")
.find(|x| {
x.children().any(|x| {
x.property("phandle").and_then(NodeProperty::as_usize)
== Some(desc.phandle as usize)
})
})
.expect("Could not find CPU node for HLIC controller");
let hart = cpu_node.property("reg").unwrap().as_usize().unwrap();
unsafe {
if CPU_INTERRUPT_HANDLERS.len() <= hart {
CPU_INTERRUPT_HANDLERS.resize(hart + 1, None);
}
assert!(
CPU_INTERRUPT_HANDLERS[hart].replace(ic_idx).is_none(),
"Conflicting HLIC interrupt handler found"
);
}
self.virq_base = *irq_idx;
for i in 0..16 {
irq_desc[self.virq_base + i].basic.ic_idx = ic_idx;
irq_desc[self.virq_base + i].basic.ic_irq = i as u32;
}
*irq_idx += 16;
Ok(())
}
fn irq_ack(&mut self) -> u32 {
panic!("Cannot ack HLIC interrupt");
}
fn irq_eoi(&mut self, _irq_num: u32) {}
fn irq_enable(&mut self, _irq_num: u32) {
// This would require IPI to a correct core
// Not bothering with this, all interrupts are enabled at all times
}
fn irq_disable(&mut self, _irq_num: u32) {
// This would require IPI to a correct core
// Not bothering with this, all interrupts are enabled at all times
}
fn irq_xlate(&self, irq_data: IrqCell) -> syscall::Result<usize> {
match irq_data {
IrqCell::L1(irq) if irq <= 0xF => Ok(self.virq_base + irq as usize),
_ => Err(Error::new(EINVAL)),
}
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if hwirq > 0 && hwirq <= 0xF {
Some(self.virq_base + hwirq as usize)
} else {
None
}
}
}
pub fn irqchip_for_hart(hart: usize) -> Option<usize> {
let value = unsafe { CPU_INTERRUPT_HANDLERS.get(hart) }?;
*value
}
+44
View File
@@ -0,0 +1,44 @@
use self::clint::Clint;
use crate::dtb::irqchip::InterruptController;
use alloc::boxed::Box;
use fdt::Fdt;
pub(crate) mod hlic;
mod plic;
#[path = "clint_sbi.rs"]
mod clint;
// pub mod clint; // actual clint.rs off limits if SBI is present
pub fn new_irqchip(ic_str: &str) -> Option<Box<dyn InterruptController>> {
if ic_str.contains("riscv,cpu-intc") {
Some(Box::new(hlic::Hlic::new()))
} else if ic_str.contains("riscv,plic0") || ic_str.contains("sifive,plic-1.0.0") {
Some(Box::new(plic::Plic::new()))
} else {
warn!("no driver for interrupt controller {:?}", ic_str);
None
}
}
pub unsafe fn init_clint(fdt: &Fdt) {
let cpus = fdt.find_node("/cpus").unwrap();
let clock_freq = cpus
.property("timebase-frequency")
.unwrap()
.as_usize()
.unwrap();
let clint_node = fdt.find_node("/soc/clint").unwrap();
assert!(clint_node
.compatible()
.unwrap()
.all()
.find(|x| (*x).eq("riscv,clint0"))
.is_some());
let clint = Clint::new(clock_freq, &clint_node);
*clint::CLINT.lock() = Some(clint);
clint::CLINT.lock().as_mut().unwrap().init(0);
}
+198
View File
@@ -0,0 +1,198 @@
use crate::{
arch::{device::irqchip::hlic, start::BOOT_HART_ID},
dtb::{
get_mmio_address,
irqchip::{InterruptController, InterruptHandler, IrqCell, IrqDesc, IRQ_CHIP},
},
sync::CleanLockToken,
};
use core::{mem, num::NonZero, sync::atomic::Ordering};
use fdt::Fdt;
use syscall::{Error, Io, Mmio, EINVAL};
#[repr(packed(4))]
#[repr(C)]
struct InterruptThresholdRegs {
threshold: Mmio<u32>,
claim_complete: Mmio<u32>,
_rsrv: [u32; 1022],
}
static MAX_CONTEXTS: usize = 64;
#[repr(packed(4))]
#[repr(C)]
struct PlicRegs {
/// source priorities
source_priority: [Mmio<u32>; 1024], // +0000 -- 0fff
// pending interrupts
pending: [Mmio<u32>; 1024], // +1000 -- 1fff
// per-context interrupt enable
enable: [[Mmio<u32>; 32]; 16320], // +2000 - 1f'ffff
// per-context priority threshold and acknowledge
thresholds: [InterruptThresholdRegs; 64], // specced at +20'0000 - 0fff'ffff for 15872 contexts
// but actual memory allotted in firmware is much lower
}
const _: () = assert!(0x1000 == mem::offset_of!(PlicRegs, pending));
const _: () = assert!(0x2000 == mem::offset_of!(PlicRegs, enable));
const _: () = assert!(0x20_0000 == mem::offset_of!(PlicRegs, thresholds));
const _: () = assert!(0x1000 == size_of::<InterruptThresholdRegs>());
impl PlicRegs {
pub fn set_priority(self: &mut Self, irq: usize, priority: usize) {
assert!(irq > 0 && irq <= 1023 && priority < 8);
self.source_priority[irq].write(priority as u32);
}
pub fn pending(self: &Self, irq_lane: usize) -> u32 {
assert!(irq_lane < 32);
self.pending[irq_lane].read()
}
pub fn enable(self: &mut Self, context: usize, irq: NonZero<usize>, enable: bool) {
assert!(irq.get() <= 1023 && context < MAX_CONTEXTS);
let irq_lane = irq.get() / 32;
let irq = irq.get() % 32;
self.enable[context][irq_lane].writef(1u32 << irq, enable);
}
pub fn set_priority_threshold(self: &mut Self, context: usize, priority: usize) {
assert!(context < MAX_CONTEXTS && priority <= 7);
self.thresholds[context].threshold.write(priority as u32);
}
pub fn claim(self: &mut Self, context: usize) -> Option<NonZero<usize>> {
assert!(context < MAX_CONTEXTS);
let claim = self.thresholds[context].claim_complete.read();
NonZero::new(claim as usize)
}
pub fn complete(self: &mut Self, context: usize, claim: NonZero<usize>) {
assert!(context < MAX_CONTEXTS);
self.thresholds[context]
.claim_complete
.write(claim.get() as u32);
}
}
pub struct Plic {
regs: *mut PlicRegs,
ndev: usize,
virq_base: usize,
context: usize,
}
impl Plic {
pub fn new() -> Self {
Self {
regs: 0 as *mut PlicRegs,
ndev: 0,
virq_base: 0,
context: 0,
}
}
}
impl InterruptHandler for Plic {
fn irq_handler(&mut self, _irq: u32, token: &mut CleanLockToken) {
unsafe {
let irq = self.irq_ack();
//println!("PLIC interrupt {}", irq);
if let Some(virq) = self.irq_to_virq(irq) {
IRQ_CHIP.trigger_virq(virq as u32, token);
} else {
error!("unexpected irq num {}", irq);
self.irq_eoi(irq);
}
}
//println!("PLIC interrupt done");
}
}
impl InterruptController for Plic {
fn irq_init(
&mut self,
fdt_opt: Option<&Fdt>,
irq_desc: &mut [IrqDesc; 1024],
ic_idx: usize,
irq_idx: &mut usize,
) -> syscall::Result<()> {
let desc = unsafe { &IRQ_CHIP.irq_chip_list.chips[ic_idx] };
let fdt = fdt_opt.unwrap();
let my_node = fdt.find_phandle(desc.phandle).unwrap();
// MMIO region
let reg = my_node.reg().unwrap().next().unwrap();
let addr = get_mmio_address(&fdt, &my_node, &reg).unwrap();
// Specifies how many external interrupts are supported by this controller.
let ndev = my_node
.property("riscv,ndev")
.and_then(|x| x.as_usize())
.unwrap();
self.regs = (addr + crate::PHYS_OFFSET) as *mut PlicRegs;
self.ndev = ndev;
self.virq_base = *irq_idx;
for i in 0..ndev {
irq_desc[self.virq_base + i].basic.ic_idx = ic_idx;
irq_desc[self.virq_base + i].basic.ic_irq = i as u32;
}
*irq_idx += ndev;
// route all interrupts to boot HART
// TODO spread irqs over all the cores when we have them?
let hlic_ic_idx = hlic::irqchip_for_hart(BOOT_HART_ID.load(Ordering::Relaxed))
.expect("Could not find HLIC irqchip for the boot hart while initing PLIC");
self.context = desc
.parents
.iter()
.position(|x| x.parent_interrupt.is_some() && x.parent == hlic_ic_idx)
.unwrap();
info!("PLIC: using context {}", self.context);
let regs = unsafe { self.regs.as_mut().unwrap() };
regs.set_priority_threshold(self.context, 0);
Ok(())
}
fn irq_ack(&mut self) -> u32 {
let regs = unsafe { self.regs.as_mut().unwrap() };
regs.claim(self.context).unwrap().get() as u32
}
fn irq_eoi(&mut self, irq_num: u32) {
let regs = unsafe { self.regs.as_mut().unwrap() };
regs.complete(self.context, NonZero::new(irq_num as usize).unwrap());
}
fn irq_enable(&mut self, irq_num: u32) {
assert!(irq_num > 0 && irq_num as usize <= self.ndev);
let regs = unsafe { self.regs.as_mut().unwrap() };
regs.set_priority(irq_num as usize, 1);
regs.enable(self.context, NonZero::new(irq_num as usize).unwrap(), true);
}
fn irq_disable(&mut self, irq_num: u32) {
assert!(irq_num > 0 && irq_num as usize <= self.ndev);
let regs = unsafe { self.regs.as_mut().unwrap() };
regs.set_priority(irq_num as usize, 1);
regs.enable(self.context, NonZero::new(irq_num as usize).unwrap(), false);
}
fn irq_xlate(&self, irq_data: IrqCell) -> syscall::Result<usize> {
match irq_data {
IrqCell::L1(irq) => Ok(self.virq_base + irq as usize),
_ => Err(Error::new(EINVAL)),
}
}
fn irq_to_virq(&self, hwirq: u32) -> Option<usize> {
if (hwirq as usize) < self.ndev {
Some(self.virq_base + hwirq as usize)
} else {
None
}
}
}
+110
View File
@@ -0,0 +1,110 @@
use crate::{
arch::{device::irqchip::hlic, time},
dtb::DTB_BINARY,
};
use fdt::{
node::{FdtNode, NodeProperty},
Fdt,
};
pub mod cpu;
pub(crate) mod irqchip;
pub mod serial;
use crate::arch::device::irqchip::init_clint;
fn string_property(name: &str) -> bool {
name == "compatible"
|| name == "model"
|| name == "device_type"
|| name == "status"
|| name == "riscv,isa-base"
|| name == "riscv,isa"
|| name == "mmu-type"
|| name == "stdout-path"
}
fn print_property(prop: &NodeProperty, n_spaces: usize) {
(0..n_spaces).for_each(|_| print!(" "));
print!("{} =", prop.name);
if string_property(prop.name)
&& let Some(str) = prop.as_str()
{
println!(" \"{}\"", str);
} else if let Some(value) = prop.as_usize() {
println!(" 0x{:08x}", value);
} else {
for v in prop.value {
print!(" {:02x}", v);
}
println!();
}
}
fn print_node(node: &FdtNode<'_, '_>, n_spaces: usize) {
(0..n_spaces).for_each(|_| print!(" "));
println!("{}/", node.name);
for prop in node.properties() {
print_property(&prop, n_spaces + 4);
}
for child in node.children() {
print_node(&child, n_spaces + 4);
}
}
pub(crate) fn dump_fdt(fdt: &Fdt) {
if let Some(root) = fdt.find_node("/") {
print_node(&root, 0);
}
}
unsafe fn init_intc(cpu: &FdtNode) {
let intc_node = cpu
.children()
.find(|x| x.name == "interrupt-controller")
.unwrap();
assert_eq!(intc_node.compatible().unwrap().first(), "riscv,cpu-intc");
// This controller is hardwired into interrupt handler code and has no Mmios
hlic::init(); // enable interrupts at HLIC level
}
pub unsafe fn init() {
unsafe {
let data = DTB_BINARY.get().unwrap();
let fdt = Fdt::new(data).unwrap();
crate::dtb::irqchip::init(&fdt);
let cpu = fdt.find_node(format!("/cpus/cpu@{}", 0).as_str()).unwrap();
init_intc(&cpu);
init_time(&fdt);
}
}
fn init_time(fdt: &Fdt) {
let cpus = fdt.find_node("/cpus").unwrap();
let clock_freq = cpus
.property("timebase-frequency")
.unwrap()
.as_usize()
.unwrap();
time::init(clock_freq);
}
pub unsafe fn init_noncore() {
unsafe {
let data = DTB_BINARY.get().unwrap();
let fdt = Fdt::new(data).unwrap();
init_clint(&fdt);
serial::init(&fdt);
}
}
pub struct ArchPercpuMisc;
impl ArchPercpuMisc {
pub const fn default() -> Self {
Self
}
}
+47
View File
@@ -0,0 +1,47 @@
use alloc::boxed::Box;
use fdt::Fdt;
pub use crate::dtb::serial::COM1;
use crate::{
dtb::{
get_interrupt, interrupt_parent,
irqchip::{register_irq, InterruptHandler, IRQ_CHIP},
},
scheme::irq::irq_trigger,
sync::CleanLockToken,
};
pub struct Com1Irq {}
impl InterruptHandler for Com1Irq {
fn irq_handler(&mut self, irq: u32, token: &mut CleanLockToken) {
COM1.lock().receive(token);
unsafe {
// FIXME add_irq accepts a u8 as irq number
// PercpuBlock::current().stats.add_irq(irq);
irq_trigger(irq.try_into().unwrap(), token);
IRQ_CHIP.irq_eoi(irq);
}
}
}
pub unsafe fn init(fdt: &Fdt) -> Option<()> {
unsafe {
if let Some(node) = fdt.find_compatible(&["ns16550a", "snps,dw-apb-uart"]) {
let intr = get_interrupt(fdt, &node, 0).unwrap();
let interrupt_parent = interrupt_parent(fdt, &node)?;
let phandle = interrupt_parent.property("phandle")?.as_usize()? as u32;
let ic_idx = IRQ_CHIP.phandle_to_ic_idx(phandle)?;
let virq = IRQ_CHIP.irq_chip_list.chips[ic_idx]
.ic
.irq_xlate(intr)
.unwrap();
info!("serial_port virq = {}", virq);
register_irq(virq as u32, Box::new(Com1Irq {}));
IRQ_CHIP.irq_enable(virq as u32);
}
// COM1.lock().enable_irq(); // FIXME receive int is enabled by default in 16550. Disable by default?
Some(())
}
}
+229
View File
@@ -0,0 +1,229 @@
use ::syscall::Exception;
use core::{arch::naked_asm, sync::atomic::Ordering};
use rmm::VirtualAddress;
use crate::{
arch::{device::irqchip, start::BOOT_HART_ID},
context::signal::excp_handler,
memory::GenericPfFlags,
ptrace,
sync::CleanLockToken,
syscall::{self, flag::*},
};
const BREAKPOINT: usize = 3;
const USERMODE_ECALL: usize = 8;
const INSTRUCTION_PAGE_FAULT: usize = 12;
const LOAD_PAGE_FAULT: usize = 13;
const STORE_PAGE_FAULT: usize = 15;
use super::InterruptStack;
#[unsafe(naked)]
// FIXME use extern "custom"
// FIXME use align(4)
pub unsafe extern "C" fn exception_handler() {
naked_asm!(
"csrrw tp, sscratch, tp",
"beq tp, x0, 3f", // exception before percpu data is available; got to be S mode
"sd t0, 0(tp)",
"csrr t0, sstatus",
"andi t0, t0, 1<<8",// SPP bit
"bne t0, x0, 2f",
// trap/interrupt from U mode, switch stacks
"ld t0, 0(tp)",
"sd sp, 0(tp)",
"ld sp, 8(tp)",
push_registers!(),
"ld t0, 0(tp)",
"sd t0, (1 * 8)(sp)", // save original SP
"csrrw t0, sscratch, tp",
"sd t0, (3 * 8)(sp)", // save original TP, and restore sscratch to handle double faults
"mv a0, sp",
"jal {0}",
// save S mode stack to percpu
"addi t0, sp, 32 * 8",
"sd t0, 8(tp)",
"li t0, 1 << 8", // return to U mode (sstatus might've been modified by nested trap or context switch)
"csrc sstatus, t0",
"j 4f",
"2: ld t0, 0(tp)", // S-mode
"3:", // S mode early
"addi sp, sp, -2 * 8", // fake stack frame for the stack tracer
push_registers!(),
"addi t1, sp, 34 * 8",
"sd t1, (1 * 8)(sp)", // save original SP
"csrrw t1, sscratch, tp",
"sd t1, (3 * 8)(sp)", // save original TP, and restore sscratch to handle double faults
"sd t0, (33 * 8)(sp)", // fill the stack frame. t0 holds original pc after push_registers
"sd fp, (32 * 8)(sp)",
"addi fp, sp, 34 * 8",
"mv a0, sp",
"jal {0}",
// return to S mode with interrupts disabled
// (sstatus might've been modified by nested trap or context switch)
"li t0, 1 << 8",
"csrs sstatus, t0",
"li t0, 1 << 5",
"csrc sstatus, t0",
"4:",
pop_registers!(),
"sret",
sym exception_handler_inner
);
}
unsafe fn exception_handler_inner(regs: &mut InterruptStack) {
unsafe {
let scause: usize;
let sstatus: usize;
core::arch::asm!(
"csrr t0, scause",
"csrr t1, sstatus",
lateout("t0") scause,
lateout("t1") sstatus,
options(nostack)
);
//info!("Exception handler incoming: sepc={:x} scause={:x} sstatus={:x}", regs.iret.sepc, scause, sstatus);
let user_mode = sstatus & (1 << 8) == 0;
if (scause as isize) < 0 {
handle_interrupt(scause & 0xF);
} else if page_fault(scause, regs, user_mode) {
} else if user_mode {
handle_user_exception(scause, regs);
} else {
handle_system_exception(scause, regs);
}
//info!("Exception handler outgoing");
}
}
unsafe fn handle_system_exception(scause: usize, regs: &InterruptStack) {
unsafe {
let stval: usize;
let tp: usize;
core::arch::asm!(
"csrr t0, stval",
"mv t1, tp",
lateout("t0") stval,
lateout("t1") tp,
options(nostack)
);
error!(
"S-mode exception! scause={:#016x}, stval={:#016x}",
scause, stval
);
if tp == 0 {
// Early failure - before misc::init and potentially before RMM init
// Do not attempt to trace stack because it would probably trap again
regs.dump();
} else {
regs.trace();
}
loop {}
}
}
unsafe fn handle_interrupt(interrupt: usize) {
unsafe {
let mut token = CleanLockToken::new();
// FIXME retrieve from percpu area
// For now all the interrupts go to boot hart so this suffices...
let hart: usize = BOOT_HART_ID.load(Ordering::Relaxed);
irqchip::hlic::interrupt(hart, interrupt, &mut token);
}
}
unsafe fn handle_user_exception(scause: usize, regs: &mut InterruptStack) {
unsafe {
let mut token = CleanLockToken::new();
if scause == USERMODE_ECALL {
let r = &mut regs.registers;
regs.iret.sepc += 4; // skip ecall
let ret = syscall::syscall(r.x17, r.x10, r.x11, r.x12, r.x13, r.x14, r.x15, &mut token);
r.x10 = ret;
return;
}
if scause == BREAKPOINT {
if ptrace::breakpoint_callback(PTRACE_STOP_BREAKPOINT, None, &mut token).is_some() {
return;
}
}
let stval: usize;
core::arch::asm!(
"csrr t0, stval",
lateout("t0") stval,
options(nostack)
);
info!(
"U-mode exception! scause={:#016x}, stval={:#016x}",
scause, stval
);
regs.dump();
// TODO
/*
let signal = match scause {
0 | 4 | 6 | 18 | 19 => SIGBUS, // misaligned / machine check
2 | 8 | 9 => SIGILL, // Illegal instruction / breakpoint / ecall
BREAKPOINT => SIGTRAP,
_ => SIGSEGV,
};
*/
excp_handler(Exception { kind: scause });
}
}
unsafe fn page_fault(scause: usize, regs: &mut InterruptStack, user_mode: bool) -> bool {
unsafe {
if scause != INSTRUCTION_PAGE_FAULT
&& scause != LOAD_PAGE_FAULT
&& scause != STORE_PAGE_FAULT
{
return false;
}
let stval: usize;
core::arch::asm!(
"csrr t0, stval",
lateout("t0") stval,
options(nostack)
);
let address = VirtualAddress::new(stval);
let mut generic_flags = GenericPfFlags::empty();
generic_flags.set(GenericPfFlags::INVOLVED_WRITE, scause == STORE_PAGE_FAULT);
generic_flags.set(GenericPfFlags::USER_NOT_SUPERVISOR, user_mode);
generic_flags.set(
GenericPfFlags::INSTR_NOT_DATA,
scause == INSTRUCTION_PAGE_FAULT,
);
// FIXME can these conditions be distinguished? Should they be?
generic_flags.set(GenericPfFlags::INVL, false);
generic_flags.set(GenericPfFlags::PRESENT, false);
crate::memory::page_fault_handler(regs, generic_flags, address).is_ok()
}
}
+332
View File
@@ -0,0 +1,332 @@
use crate::{memory::ArchIntCtx, panic, syscall::IntRegisters};
#[derive(Default)]
#[repr(C)]
pub struct Registers {
pub x1: usize, // ra
pub x2: usize, // sp
pub x3: usize, // gp
pub x4: usize, // tp
pub x5: usize, // t0
pub x6: usize, // t1
pub x7: usize, // t2
pub x8: usize, // s0/fp
pub x9: usize, // s1
pub x10: usize, // a0...
pub x11: usize,
pub x12: usize,
pub x13: usize,
pub x14: usize,
pub x15: usize,
pub x16: usize,
pub x17: usize, // a7
pub x18: usize, // s2...
pub x19: usize,
pub x20: usize,
pub x21: usize,
pub x22: usize,
pub x23: usize,
pub x24: usize,
pub x25: usize,
pub x26: usize,
pub x27: usize, // s11
pub x28: usize, // t3...
pub x29: usize,
pub x30: usize,
pub x31: usize, // t6
}
impl Registers {
pub fn dump(&self) {
println!("X1: {:>016X}", { self.x1 });
println!("X2: {:>016X}", { self.x2 });
println!("X3: {:>016X}", { self.x3 });
println!("X4: {:>016X}", { self.x4 });
println!("X5: {:>016X}", { self.x5 });
println!("X6: {:>016X}", { self.x6 });
println!("X7: {:>016X}", { self.x7 });
println!("X8: {:>016X}", { self.x8 });
println!("X9: {:>016X}", { self.x9 });
println!("X10: {:>016X}", { self.x10 });
println!("X11: {:>016X}", { self.x11 });
println!("X12: {:>016X}", { self.x12 });
println!("X13: {:>016X}", { self.x13 });
println!("X14: {:>016X}", { self.x14 });
println!("X15: {:>016X}", { self.x15 });
println!("X16: {:>016X}", { self.x16 });
println!("X17: {:>016X}", { self.x17 });
println!("X18: {:>016X}", { self.x18 });
println!("X19: {:>016X}", { self.x19 });
println!("X20: {:>016X}", { self.x20 });
println!("X21: {:>016X}", { self.x21 });
println!("X22: {:>016X}", { self.x22 });
println!("X23: {:>016X}", { self.x23 });
println!("X24: {:>016X}", { self.x24 });
println!("X25: {:>016X}", { self.x25 });
println!("X26: {:>016X}", { self.x26 });
println!("X27: {:>016X}", { self.x27 });
println!("X28: {:>016X}", { self.x28 });
println!("X29: {:>016X}", { self.x29 });
println!("X30: {:>016X}", { self.x30 });
println!("X31: {:>016X}", { self.x31 });
}
}
#[derive(Default)]
#[repr(C)]
pub struct IretRegisters {
pub sepc: usize,
}
impl IretRegisters {
pub fn dump(&self) {
println!("SEPC: {:>016X}", { self.sepc });
}
}
// NOTE: Layout of this structure must be synced with assembly code in exception.rs
#[derive(Default)]
#[repr(C)]
pub struct InterruptStack {
pub registers: Registers,
pub iret: IretRegisters,
}
impl InterruptStack {
pub fn init(&mut self) {
const {
assert!(32 * 8 == size_of::<InterruptStack>());
}
}
pub fn frame_pointer(&self) -> usize {
self.registers.x8
}
pub fn stack_pointer(&self) -> usize {
self.registers.x2
}
pub fn set_stack_pointer(&mut self, sp: usize) {
self.registers.x2 = sp;
}
pub fn set_instr_pointer(&mut self, ip: usize) {
self.iret.sepc = ip;
}
pub fn instr_pointer(&self) -> usize {
self.iret.sepc
}
pub fn sig_archdep_reg(&self) -> usize {
self.registers.x5
}
pub fn set_syscall_ret_reg(&mut self, ret: usize) {
self.registers.x10 = ret;
}
pub fn set_arg1(&mut self, arg_opt: Option<usize>) {
if let Some(arg) = arg_opt {
self.registers.x11 = arg;
}
}
pub fn dump(&self) {
self.iret.dump();
self.registers.dump();
}
pub fn trace(&self) {
self.dump();
unsafe {
panic::user_stack_trace(&self);
panic::stack_trace();
}
}
/// Saves all registers to a struct used by the proc:
/// scheme to read/write registers.
pub fn save(&self, all: &mut IntRegisters) {
all.pc = self.iret.sepc;
all.x31 = self.registers.x31;
all.x30 = self.registers.x30;
all.x29 = self.registers.x29;
all.x28 = self.registers.x28;
all.x27 = self.registers.x27;
all.x26 = self.registers.x26;
all.x25 = self.registers.x25;
all.x24 = self.registers.x24;
all.x23 = self.registers.x23;
all.x22 = self.registers.x22;
all.x21 = self.registers.x21;
all.x20 = self.registers.x20;
all.x19 = self.registers.x19;
all.x18 = self.registers.x18;
all.x17 = self.registers.x17;
all.x16 = self.registers.x16;
all.x15 = self.registers.x15;
all.x14 = self.registers.x14;
all.x13 = self.registers.x13;
all.x12 = self.registers.x12;
all.x11 = self.registers.x11;
all.x10 = self.registers.x10;
all.x9 = self.registers.x9;
all.x8 = self.registers.x8;
all.x7 = self.registers.x7;
all.x6 = self.registers.x6;
all.x5 = self.registers.x5;
all.x2 = self.registers.x2;
all.x1 = self.registers.x1;
}
/// Loads all registers from a struct used by the proc:
/// scheme to read/write registers.
pub fn load(&mut self, all: &IntRegisters) {
self.iret.sepc = all.pc;
self.registers.x31 = all.x31;
self.registers.x30 = all.x30;
self.registers.x29 = all.x29;
self.registers.x28 = all.x28;
self.registers.x27 = all.x27;
self.registers.x26 = all.x26;
self.registers.x25 = all.x25;
self.registers.x24 = all.x24;
self.registers.x23 = all.x23;
self.registers.x22 = all.x22;
self.registers.x21 = all.x21;
self.registers.x20 = all.x20;
self.registers.x19 = all.x19;
self.registers.x18 = all.x18;
self.registers.x17 = all.x17;
self.registers.x16 = all.x16;
self.registers.x15 = all.x15;
self.registers.x14 = all.x14;
self.registers.x13 = all.x13;
self.registers.x12 = all.x12;
self.registers.x11 = all.x11;
self.registers.x10 = all.x10;
self.registers.x9 = all.x9;
self.registers.x8 = all.x8;
self.registers.x7 = all.x7;
self.registers.x6 = all.x6;
self.registers.x5 = all.x5;
self.registers.x2 = all.x2;
self.registers.x1 = all.x1;
}
//TODO
pub fn is_singlestep(&self) -> bool {
false
}
pub fn set_singlestep(&mut self, _singlestep: bool) {}
}
impl ArchIntCtx for InterruptStack {
fn ip(&self) -> usize {
self.iret.sepc
}
fn recover_and_efault(&mut self) {
// Set the return value to nonzero to indicate usercopy failure (EFAULT), and emulate the
// return instruction by setting the return pointer to the saved LR value.
self.iret.sepc = self.registers.x1; // ra
self.registers.x10 = 1; // a0
}
}
/// Except for sp and tp
#[macro_export]
macro_rules! push_registers {
() => {
"
addi sp, sp, -32 * 8
sd x1, (0 * 8)(sp)
// skip sp
sd x3, (2 * 8)(sp)
// skip tp
sd x5, (4 * 8)(sp)
sd x6, (5 * 8)(sp)
sd x7, (6 * 8)(sp)
sd x8, (7 * 8)(sp)
sd x9, (8 * 8)(sp)
sd x10, (9 * 8)(sp)
sd x11, (10 * 8)(sp)
sd x12, (11 * 8)(sp)
sd x13, (12 * 8)(sp)
sd x14, (13 * 8)(sp)
sd x15, (14 * 8)(sp)
sd x16, (15 * 8)(sp)
sd x17, (16 * 8)(sp)
sd x18, (17 * 8)(sp)
sd x19, (18 * 8)(sp)
sd x20, (19 * 8)(sp)
sd x21, (20 * 8)(sp)
sd x22, (21 * 8)(sp)
sd x23, (22 * 8)(sp)
sd x24, (23 * 8)(sp)
sd x25, (24 * 8)(sp)
sd x26, (25 * 8)(sp)
sd x27, (26 * 8)(sp)
sd x28, (27 * 8)(sp)
sd x29, (28 * 8)(sp)
sd x30, (29 * 8)(sp)
sd x31, (30 * 8)(sp)
csrr t0, sepc
sd t0, (31 * 8)(sp)
"
}; // keep sepc value in t0 on exit
}
#[macro_export]
macro_rules! pop_registers {
() => {
"
ld t0, (31 * 8)(sp)
csrw sepc, t0
ld x1, (0 * 8)(sp)
// skip sp, it'll be restored later
ld x3, (2 * 8)(sp)
ld x4, (3 * 8)(sp)
ld x5, (4 * 8)(sp)
ld x6, (5 * 8)(sp)
ld x7, (6 * 8)(sp)
ld x8, (7 * 8)(sp)
ld x9, (8 * 8)(sp)
ld x10, (9 * 8)(sp)
ld x11, (10 * 8)(sp)
ld x12, (11 * 8)(sp)
ld x13, (12 * 8)(sp)
ld x14, (13 * 8)(sp)
ld x15, (14 * 8)(sp)
ld x16, (15 * 8)(sp)
ld x17, (16 * 8)(sp)
ld x18, (17 * 8)(sp)
ld x19, (18 * 8)(sp)
ld x20, (19 * 8)(sp)
ld x21, (20 * 8)(sp)
ld x22, (21 * 8)(sp)
ld x23, (22 * 8)(sp)
ld x24, (23 * 8)(sp)
ld x25, (24 * 8)(sp)
ld x26, (25 * 8)(sp)
ld x27, (26 * 8)(sp)
ld x28, (27 * 8)(sp)
ld x29, (28 * 8)(sp)
ld x30, (29 * 8)(sp)
ld x31, (30 * 8)(sp)
ld sp, (1 * 8)(sp)
"
};
}
#[unsafe(naked)]
pub unsafe extern "C" fn enter_usermode() -> ! {
core::arch::naked_asm!(
"jalr s11",
"li t0, 1 << 8", // force U mode on sret
"csrc sstatus, t0",
"li t0, 0x6000", // set FS to dirty (enable FPU in U mode)
"csrs sstatus, t0",
"addi t0, sp, 32 * 8", // save S mode stack to percpu
"sd t0, 8(tp)",
pop_registers!(),
"sret",
)
}
+39
View File
@@ -0,0 +1,39 @@
use core::arch::asm;
#[macro_use]
mod handler;
mod exception;
pub mod syscall;
pub mod trace;
pub use exception::exception_handler;
pub use handler::InterruptStack;
/// Clear interrupts
#[inline(always)]
pub unsafe fn disable() {
unsafe { asm!("csrci sstatus, 1 << 1") }
}
/// Set interrupts and halt
/// This will atomically wait for the next interrupt
/// Performing enable followed by halt is not guaranteed to be atomic, use this instead!
#[inline(always)]
pub unsafe fn enable_and_halt() {
unsafe { asm!("wfi", "csrsi sstatus, 1 << 1", "nop") }
}
/// Set interrupts and nop
/// This will enable interrupts and allow the IF flag to be processed
/// Simply enabling interrupts does not gurantee that they will trigger, use this instead!
#[inline(always)]
pub unsafe fn enable_and_nop() {
unsafe { asm!("csrsi sstatus, 1 << 1", "nop") }
}
/// Halt instruction
#[inline(always)]
pub unsafe fn halt() {
unsafe { asm!("wfi", options(nomem, nostack)) }
}
+1
View File
@@ -0,0 +1 @@
pub use super::handler::enter_usermode;
+35
View File
@@ -0,0 +1,35 @@
use core::arch::asm;
pub struct StackTrace {
pub fp: usize,
pub pc_ptr: *const usize,
}
impl StackTrace {
#[inline(always)]
pub unsafe fn start() -> Option<Self> {
unsafe {
let fp: usize;
asm!("mv {}, fp", out(reg) fp);
let pc_ptr = fp.checked_sub(size_of::<usize>())?;
let fp = pc_ptr.checked_sub(size_of::<usize>())?;
Some(StackTrace {
fp,
pc_ptr: pc_ptr as *const usize,
})
}
}
pub unsafe fn next(self) -> Option<Self> {
unsafe {
let fp = *(self.fp as *const usize);
let pc_ptr = fp.checked_sub(size_of::<usize>())?;
let fp = pc_ptr.checked_sub(size_of::<usize>())?;
Some(StackTrace {
fp: fp,
pc_ptr: pc_ptr as *const usize,
})
}
}
}
+34
View File
@@ -0,0 +1,34 @@
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum IpiKind {
Wakeup = 0x40,
Tlb = 0x41,
Switch = 0x42,
Pit = 0x43,
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum IpiTarget {
Current = 1,
All = 2,
Other = 3,
}
#[inline(always)]
pub fn ipi(_kind: IpiKind, _target: IpiTarget) {
if cfg!(not(feature = "multi_core")) {
return;
}
// FIXME implement
}
#[inline(always)]
pub fn ipi_single(_kind: IpiKind, _target: &crate::percpu::PercpuBlock) {
if cfg!(not(feature = "multi_core")) {
return;
}
// FIXME implement
}
+47
View File
@@ -0,0 +1,47 @@
use core::arch::asm;
use crate::{
cpu_set::LogicalCpuId,
memory::{RmmA, RmmArch},
percpu::PercpuBlock,
};
#[repr(C)]
pub struct ArchPercpu {
// These fields must be kept first and in this order. Assembly in exception.rs depends on it
pub tmp: usize,
pub s_sp: usize,
pub percpu: PercpuBlock,
}
impl PercpuBlock {
pub fn current() -> &'static Self {
unsafe {
let tp: *const ArchPercpu;
asm!( "mv t0, tp", out("t0") tp );
let arch_percpu = &*tp;
&arch_percpu.percpu
}
}
}
#[cold]
pub unsafe fn init(cpu_id: LogicalCpuId) {
unsafe {
let frame = crate::memory::allocate_frame().expect("failed to allocate percpu memory");
let virt = RmmA::phys_to_virt(frame.base()).data() as *mut ArchPercpu;
virt.write(ArchPercpu {
tmp: 0,
s_sp: 0,
percpu: PercpuBlock::init(cpu_id),
});
asm!(
"mv tp, {}",
"csrw sscratch, tp",
in(reg) virt as usize
);
}
}
+69
View File
@@ -0,0 +1,69 @@
pub mod consts;
pub mod debug;
pub mod device;
pub mod interrupt;
pub mod ipi;
pub mod misc;
pub mod paging;
pub mod start;
pub mod stop;
pub mod time;
pub use ::rmm::riscv64::RiscV64Sv39Arch as CurrentRmmArch;
use core::arch::naked_asm;
pub use arch_copy_to_user as arch_copy_from_user;
#[unsafe(naked)]
pub unsafe extern "C" fn arch_copy_to_user(dst: usize, src: usize, len: usize) -> u8 {
naked_asm!(
"
.global __usercopy_start
__usercopy_start:
addi sp, sp, -16
sd fp, 0(sp)
sd ra, 8(sp)
addi fp, sp, 16
li t1, 1 << 18 // SUM
csrs sstatus, t1
jal 2f
csrc sstatus, t1
ld ra, -8(fp)
ld fp, -16(fp)
addi sp, sp, 16
ret
2: or t0, a0, a1
andi t0, t0, 7
bne t0, x0, 4f
srli t2, a2, 3
andi a2, a2, 7
beq t2, x0, 4f
3: ld t0, 0(a1)
sd t0, 0(a0)
addi a0, a0, 8
addi a1, a1, 8
addi t2, t2, -1
bne t2, x0, 3b
4: beq a2, x0, 5f
lb t0, 0(a1)
sb t0, 0(a0)
addi a0, a0, 1
addi a1, a1, 1
addi a2, a2, -1
bne a2, x0, 4b
5: mv a0, x0
ret
.global __usercopy_end
__usercopy_end:
"
)
}
pub const KFX_SIZE: usize = 1024;
// This function exists as the KFX size is dynamic on x86_64.
pub fn kfx_size() -> usize {
KFX_SIZE
}
+5
View File
@@ -0,0 +1,5 @@
#[cold]
pub unsafe fn init() {
// Assuming SBI already set up PMAs correctly for us
// TODO: detect Svpbmt present/enabled and override device memory with PBMT=IO
}
+140
View File
@@ -0,0 +1,140 @@
use core::{
arch::naked_asm,
cell::SyncUnsafeCell,
sync::atomic::{AtomicUsize, Ordering},
};
use crate::{
allocator,
arch::{device, interrupt::exception_handler, paging},
devices::graphical_debug,
dtb::serial::init_early,
startup::KernelArgs,
};
/// Test of zero values in BSS.
static mut BSS_TEST_ZERO: usize = 0;
/// Test of non-zero values in data.
static mut DATA_TEST_NONZERO: usize = 0xFFFF_FFFF_FFFF_FFFF;
pub static BOOT_HART_ID: AtomicUsize = AtomicUsize::new(0);
fn get_boot_hart_id(env: &[u8]) -> Option<usize> {
for line in core::str::from_utf8(env).unwrap_or("").lines() {
let mut parts = line.splitn(2, '=');
let name = parts.next().unwrap_or("");
let value = parts.next().unwrap_or("");
if name == "BOOT_HART_ID" {
return usize::from_str_radix(value, 16).ok();
}
}
None
}
#[repr(C, align(16))]
struct StackAlign<T>(T);
static STACK: SyncUnsafeCell<StackAlign<[u8; 128 * 1024]>> =
SyncUnsafeCell::new(StackAlign([0; 128 * 1024]));
// FIXME use extern "custom"
#[unsafe(naked)]
#[unsafe(no_mangle)]
extern "C" fn kstart() {
naked_asm!("
mv gp, x0 // ensure gp relative accesses crash
mv tp, x0 // reset percpu until it is initialized
csrw sscratch, tp
// BSS should already be zero
ld t0, {bss_test_zero}
bnez t0, .Lkstart_crash
ld t0, {data_test_nonzero}
beqz t0, .Lkstart_crash
.Lpcrel_hi0:
auipc sp, %pcrel_hi({stack}+{stack_size}-16)
addi sp, sp, %pcrel_lo(.Lpcrel_hi0)
la t0, {exception_handler} // WARL=0 - direct mode combined handler
csrw stvec, t0
li ra, 0
j {start}
.Lkstart_crash:
jr x0
",
bss_test_zero = sym BSS_TEST_ZERO,
data_test_nonzero = sym DATA_TEST_NONZERO,
exception_handler = sym exception_handler,
stack = sym STACK,
stack_size = const size_of_val(&STACK),
start = sym start,
);
}
/// The entry to Rust, all things must be initialized
unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! {
unsafe {
let bootstrap = {
let args = args_ptr.read();
let dtb_data = if args.hwdesc_base != 0 {
Some((
crate::PHYS_OFFSET + args.hwdesc_base as usize,
args.hwdesc_size as usize,
))
} else {
None
};
let dtb = args.dtb();
graphical_debug::init(args.env());
if let Some(dtb) = &dtb {
init_early(dtb);
}
info!("Redox OS starting...");
args.print();
if let Some(dtb) = &dtb {
device::dump_fdt(&dtb);
}
// Initialize RMM
crate::startup::memory::init(&args, None, None);
let boot_hart_id =
get_boot_hart_id(args.env()).expect("Didn't get boot HART id from bootloader");
info!("Booting on HART {}", boot_hart_id);
BOOT_HART_ID.store(boot_hart_id, Ordering::Relaxed);
paging::init();
crate::arch::misc::init(crate::cpu_set::LogicalCpuId::new(0));
// Setup kernel heap
allocator::init();
// Activate memory logging
crate::log::init();
crate::dtb::init(dtb_data);
// Initialize devices
device::init();
// Initialize all of the non-core devices not otherwise needed to complete initialization
device::init_noncore();
// FIXME bringup AP HARTs
args.bootstrap()
};
crate::startup::kmain(bootstrap);
}
}
+15
View File
@@ -0,0 +1,15 @@
use crate::sync::CleanLockToken;
pub unsafe fn kreset() -> ! {
println!("kreset");
unimplemented!()
}
pub unsafe fn emergency_reset() -> ! {
unimplemented!()
}
pub unsafe fn kstop(_token: &mut CleanLockToken) -> ! {
println!("kstop");
unimplemented!()
}
+34
View File
@@ -0,0 +1,34 @@
use core::{
arch::asm,
sync::atomic::{AtomicUsize, Ordering},
};
use crate::sync::CleanLockToken;
static MTIME_FREQ_HZ: AtomicUsize = AtomicUsize::new(0);
pub fn init(freq_hz: usize) {
MTIME_FREQ_HZ.store(freq_hz, Ordering::Relaxed);
}
pub fn monotonic_absolute(_token: &mut CleanLockToken) -> u128 {
let freq_hz = MTIME_FREQ_HZ.load(Ordering::Relaxed);
if freq_hz > 0 {
let counter: usize;
unsafe {
asm!(
"rdtime t0",
lateout("t0") counter
);
};
counter as u128 * 1_000_000_000u128 / freq_hz as u128
} else {
0
}
}
pub fn monotonic_resolution() -> u128 {
let freq_hz = MTIME_FREQ_HZ.load(Ordering::Relaxed);
1_000_000_000u128 / freq_hz as u128
}
+20
View File
@@ -0,0 +1,20 @@
// Because the memory map is so important to not be aliased, it is defined here, in one place
// The lower 256 PML4 entries are reserved for userspace
// Each PML4 entry references up to 512 GB of memory
// The second from the top (510) PML4 is reserved for the kernel
// Framebuffer mapped by bootloader to 0xD000_0000 (128 MiB max)
// Offset to APIC mappings (optional)
pub const LAPIC_OFFSET: usize = 0xD800_0000;
pub const IOAPIC_OFFSET: usize = LAPIC_OFFSET + 4096;
pub const HPET_OFFSET: usize = IOAPIC_OFFSET + 4096;
/// Offset to kernel heap (256 MiB max)
#[inline(always)]
pub fn kernel_heap_offset() -> usize {
0xE000_0000
}
/// End offset of the user image, i.e. kernel start
pub const USER_END_OFFSET: usize = 0x8000_0000;
+471
View File
@@ -0,0 +1,471 @@
use crate::{arch::flags::FLAG_SINGLESTEP, memory::ArchIntCtx, panic, syscall::IntRegisters};
#[derive(Default)]
#[repr(C, packed)]
pub struct ScratchRegisters {
pub edx: usize,
pub ecx: usize,
pub eax: usize,
}
impl ScratchRegisters {
pub fn dump(&self) {
println!("EAX: {:08x}", { self.eax });
println!("ECX: {:08x}", { self.ecx });
println!("EDX: {:08x}", { self.edx });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct PreservedRegisters {
pub ebp: usize,
pub esi: usize,
pub edi: usize,
pub ebx: usize,
}
impl PreservedRegisters {
pub fn dump(&self) {
println!("EBX: {:08x}", { self.ebx });
println!("EDI: {:08x}", { self.edi });
println!("ESI: {:08x}", { self.esi });
println!("EBP: {:08x}", { self.ebp });
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct IretRegisters {
pub eip: usize,
pub cs: usize,
pub eflags: usize,
// ----
// The following will only be present if interrupt is raised from another
// privilege ring. Otherwise, they are undefined values.
// ----
pub esp: usize,
pub ss: usize,
}
impl IretRegisters {
pub fn dump(&self) {
println!("EFLAG: {:08x}", { self.eflags });
println!("CS: {:08x}", { self.cs });
println!("EIP: {:08x}", { self.eip });
if self.cs & 0b11 != 0b00 {
println!("ESP: {:08x}", { self.esp });
println!("SS: {:08x}", { self.ss });
}
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct InterruptStack {
pub gs: usize,
pub preserved: PreservedRegisters,
pub scratch: ScratchRegisters,
pub iret: IretRegisters,
}
impl InterruptStack {
pub fn init(&mut self) {
// Always enable interrupts!
self.iret.eflags = x86::bits32::eflags::EFlags::FLAGS_IF.bits() as usize;
self.iret.ss = (crate::arch::gdt::GDT_USER_DATA << 3) | 3;
self.iret.cs = (crate::arch::gdt::GDT_USER_CODE << 3) | 3;
self.gs = (crate::arch::gdt::GDT_USER_GS << 3) | 3;
}
pub fn dump(&self) {
self.iret.dump();
self.scratch.dump();
self.preserved.dump();
}
pub fn trace(&self) {
self.dump();
unsafe {
panic::user_stack_trace(&self);
panic::stack_trace();
}
}
/// Saves all registers to a struct used by the proc:
/// scheme to read/write registers.
pub fn save(&self, all: &mut IntRegisters) {
all.ebp = self.preserved.ebp;
all.esi = self.preserved.esi;
all.edi = self.preserved.edi;
all.ebx = self.preserved.ebx;
all.edx = self.scratch.edx;
all.ecx = self.scratch.ecx;
all.eax = self.scratch.eax;
all.eip = self.iret.eip;
all.cs = self.iret.cs;
all.eflags = self.iret.eflags;
// Set esp and ss:
const CPL_MASK: usize = 0b11;
let cs: usize;
unsafe {
core::arch::asm!("mov {}, cs", out(reg) cs);
}
if self.iret.cs & CPL_MASK == cs & CPL_MASK {
// Privilege ring didn't change, so neither did the stack
all.esp = self as *const Self as usize // esp after Self was pushed to the stack
+ size_of::<Self>() // disregard Self
- size_of::<usize>() * 2; // well, almost: esp and ss need to be excluded as they aren't present
unsafe {
core::arch::asm!("mov {}, ss", out(reg) all.ss);
}
} else {
all.esp = self.iret.esp;
all.ss = self.iret.ss;
}
}
pub fn frame_pointer(&self) -> usize {
self.preserved.ebp
}
pub fn stack_pointer(&self) -> usize {
self.iret.esp
}
pub fn set_stack_pointer(&mut self, esp: usize) {
self.iret.esp = esp;
}
pub fn instr_pointer(&self) -> usize {
self.iret.eip
}
pub fn sig_archdep_reg(&self) -> usize {
self.iret.eflags
}
pub fn set_instr_pointer(&mut self, eip: usize) {
self.iret.eip = eip;
}
/// Loads all registers from a struct used by the proc:
/// scheme to read/write registers.
pub fn load(&mut self, all: &IntRegisters) {
// TODO: Which of these should be allowed to change?
self.preserved.ebp = all.ebp;
self.preserved.esi = all.esi;
self.preserved.edi = all.edi;
self.preserved.ebx = all.ebx;
self.scratch.edx = all.edx;
self.scratch.ecx = all.ecx;
self.scratch.eax = all.eax;
self.iret.eip = all.eip;
// FIXME: The interrupt stack on which this is called, is always from userspace, but make
// the API safer.
self.iret.esp = all.esp;
// OF, DF, 0, TF => D
// SF, ZF, 0, AF => D
// 0, PF, 1, CF => 5
const ALLOWED_EFLAGS: usize = 0xDD5;
// These should probably be restricted
// self.iret.cs = all.cs;
self.iret.eflags &= !ALLOWED_EFLAGS;
self.iret.eflags |= all.eflags & ALLOWED_EFLAGS;
}
/// Enables the "Trap Flag" in the FLAGS register, causing the CPU
/// to send a Debug exception after the next instruction. This is
/// used for singlestep in the proc: scheme.
pub fn set_singlestep(&mut self, enabled: bool) {
if enabled {
self.iret.eflags |= FLAG_SINGLESTEP;
} else {
self.iret.eflags &= !FLAG_SINGLESTEP;
}
}
}
#[derive(Default)]
#[repr(C, packed)]
pub struct InterruptErrorStack {
pub code: usize,
pub inner: InterruptStack,
}
impl InterruptErrorStack {
pub fn dump(&self) {
println!("CODE: {:08x}", { self.code });
self.inner.dump();
}
pub fn trace(&self) {
self.dump();
unsafe {
panic::user_stack_trace(&self.inner);
panic::stack_trace();
}
}
}
#[macro_export]
macro_rules! push_scratch {
() => {
"
// Push scratch registers (minus eax)
push ecx
push edx
"
};
}
#[macro_export]
macro_rules! pop_scratch {
() => {
"
// Pop scratch registers
pop edx
pop ecx
pop eax
"
};
}
#[macro_export]
macro_rules! push_preserved {
() => {
"
// Push preserved registers
push ebx
push edi
push esi
push ebp
"
};
}
#[macro_export]
macro_rules! pop_preserved {
() => {
"
// Pop preserved registers
pop ebp
pop esi
pop edi
pop ebx
"
};
}
// Must always happen after push_scratch
macro_rules! enter_gs {
() => {
"
// Enter kernel GS segment
mov ecx, gs
push ecx
mov ecx, 0x18
mov gs, ecx
"
};
}
// Must always happen before pop_scratch
macro_rules! exit_gs {
() => {
"
// Exit kernel GS segment
pop ecx
mov gs, ecx
"
};
}
#[macro_export]
macro_rules! interrupt_stack {
// XXX: Apparently we cannot use $expr and check for bool exhaustiveness, so we will have to
// use idents directly instead.
($name:ident, |$stack:ident| $code:block) => {
#[unsafe(naked)]
pub unsafe extern "C" fn $name() {
unsafe extern "fastcall" fn inner($stack: &mut $crate::arch::x86::interrupt::InterruptStack) {
// TODO: Force the declarations to specify unsafe?
#[allow(unused_unsafe)]
unsafe {
$code
}
}
core::arch::naked_asm!(
// Backup all userspace registers to stack
"push eax",
push_scratch!(),
push_preserved!(),
// Enter kernel TLS segment
enter_gs!(),
// TODO: Map PTI
// $crate::arch::x86::pti::map();
// Call inner function with pointer to stack
"
mov ecx, esp
call {inner}
",
// TODO: Unmap PTI
// $crate::arch::x86::pti::unmap();
// Exit kernel TLS segment
exit_gs!(),
// Restore all userspace registers
pop_preserved!(),
pop_scratch!(),
"iretd",
inner = sym inner,
);
}
};
($name:ident, |$stack:ident| $code:block) => { interrupt_stack!($name, |$stack| $code); };
($name:ident, @paranoid, |$stack:ident| $code:block) => { interrupt_stack!($name, |$stack| $code); }
}
#[macro_export]
macro_rules! interrupt {
($name:ident, || $code:block) => {
#[unsafe(naked)]
pub unsafe extern "C" fn $name() {
unsafe extern "C" fn inner() {
$code
}
core::arch::naked_asm!(
// Backup all userspace registers to stack
"push eax",
push_scratch!(),
// Enter kernel TLS segment
enter_gs!(),
// TODO: Map PTI
// $crate::arch::x86::pti::map();
// Call inner function with pointer to stack
"call {inner}",
// TODO: Unmap PTI
// $crate::arch::x86::pti::unmap();
// Exit kernel TLS segment
exit_gs!(),
// Restore all userspace registers
pop_scratch!(),
"iretd",
inner = sym inner,
);
}
};
}
#[macro_export]
macro_rules! interrupt_error {
($name:ident, |$stack:ident, $error_code:ident| $code:block) => {
#[unsafe(naked)]
pub unsafe extern "C" fn $name() {
unsafe extern "C" fn inner($stack: &mut $crate::arch::x86::interrupt::handler::InterruptErrorStack) {
let $error_code: usize = $stack.code;
$code
}
core::arch::naked_asm!(
// Move eax into code's place, put code in last instead (to be
// compatible with InterruptStack)
"xchg [esp], eax",
// Push all userspace registers
push_scratch!(),
push_preserved!(),
// Enter kernel TLS segment
enter_gs!(),
// Put code in, it's now in eax
"push eax",
// TODO: Map PTI
// $crate::arch::x86::pti::map();
// Call inner function with pointer to stack
"
push esp
call {inner}
",
// add esp, 4
// TODO: Unmap PTI (split "add esp, 8" into two "add esp, 4"s maybe?)
// $crate::arch::x86::pti::unmap();
// Pop previous esp and code
"add esp, 8",
// Exit kernel TLS segment
exit_gs!(),
// Restore all userspace registers
pop_preserved!(),
pop_scratch!(),
// The error code has already been popped, so use the regular macro.
"iretd",
inner = sym inner,
);
}
};
}
#[unsafe(naked)]
unsafe extern "C" fn usercopy_trampoline() {
core::arch::naked_asm!(
"
pop esi
pop edi
mov eax, 1
ret
"
);
}
impl ArchIntCtx for InterruptStack {
fn ip(&self) -> usize {
self.iret.eip
}
fn recover_and_efault(&mut self) {
// Unlike on x86_64, Protected Mode interrupts will not save/restore esp and ss unless
// privilege rings changed, which they won't here as we are catching a kernel-induced page
// fault.
//
// Thus, it is only possible to change scratch/preserved registers, and EIP. While it may
// be feasible to set ECX to zero to stop the REP MOVSB, or increase EIP by 2 (REP MOVSB is
// f3 a4, i.e. 2 bytes), this trampoline allows any memcpy implementation, that reasonably
// pushes preserved registers to the stack.
self.iret.eip = usercopy_trampoline as usize;
}
}
#[unsafe(naked)]
pub unsafe extern "C" fn enter_usermode() {
core::arch::naked_asm!(
// TODO: Unmap PTI
// $crate::arch::x86::pti::unmap();
// Exit kernel TLS segment
exit_gs!(),
// Restore all userspace registers
pop_preserved!(),
pop_scratch!(),
"iretd",
)
}
+10
View File
@@ -0,0 +1,10 @@
//! Interrupt instructions
pub use crate::arch::x86_shared::interrupt::*;
#[macro_use]
pub mod handler;
pub mod syscall;
pub use self::handler::InterruptStack;
+48
View File
@@ -0,0 +1,48 @@
use crate::{
ptrace,
sync::CleanLockToken,
syscall,
syscall::flag::{PTRACE_FLAG_IGNORE, PTRACE_STOP_POST_SYSCALL, PTRACE_STOP_PRE_SYSCALL},
};
pub unsafe fn init() {}
macro_rules! with_interrupt_stack {
(|$stack:ident, $token:ident| $code:block) => {{
let mut $token = CleanLockToken::new();
let allowed = ptrace::breakpoint_callback(PTRACE_STOP_PRE_SYSCALL, None, &mut $token)
.and_then(|_| ptrace::next_breakpoint().map(|f| !f.contains(PTRACE_FLAG_IGNORE)));
if allowed.unwrap_or(true) {
// If the syscall is `clone`, the clone won't return here. Instead,
// it'll return early and leave any undropped values. This is
// actually GOOD, because any references are at that point UB
// anyway, because they are based on the wrong stack.
let $stack = &mut *$stack;
$code
}
ptrace::breakpoint_callback(PTRACE_STOP_POST_SYSCALL, None, &mut $token);
}};
}
interrupt_stack!(syscall, |stack| {
with_interrupt_stack!(|stack, token| {
let scratch = &stack.scratch;
let preserved = &stack.preserved;
let ret = syscall::syscall(
scratch.eax,
preserved.ebx,
scratch.ecx,
scratch.edx,
preserved.esi,
preserved.edi,
preserved.ebp,
&mut token,
);
stack.scratch.eax = ret;
})
});
pub use super::handler::enter_usermode;
+41
View File
@@ -0,0 +1,41 @@
pub use crate::arch::x86_shared::*;
/// Constants like memory locations
pub mod consts;
/// Interrupt instructions
#[macro_use]
pub mod interrupt;
#[unsafe(naked)]
pub unsafe extern "C" fn arch_copy_to_user(dst: usize, src: usize, len: usize) -> u8 {
core::arch::naked_asm!(
"
.global __usercopy_start
__usercopy_start:
push edi
push esi
mov edi, [esp + 12] # dst
mov esi, [esp + 16] # src
mov ecx, [esp + 20] # len
rep movsb
pop esi
pop edi
xor eax, eax
ret
.global __usercopy_end
__usercopy_end:
"
);
}
pub use arch_copy_to_user as arch_copy_from_user;
pub const KFX_SIZE: usize = 512;
// This function exists as the KFX size is dynamic on x86_64.
pub fn kfx_size() -> usize {
KFX_SIZE
}
+317
View File
@@ -0,0 +1,317 @@
#![allow(unused_imports)]
use spin::Once;
use x86::controlregs::{Cr4, Xcr0};
use crate::{
arch::cpuid::{cpuid, feature_info, has_ext_feat},
context::memory::PageSpan,
memory::{KernelMapper, Page, PageFlags, VirtualAddress, PAGE_SIZE},
};
#[cfg(all(cpu_feature_never = "xsave", not(cpu_feature_never = "xsaveopt")))]
compile_error!("cannot force-disable xsave without force-disabling xsaveopt");
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct AltReloc {
// These two fields point to a utf-8 name of the feature, see the match statement below.
pub name_start: *const u8,
pub name_len: usize,
// Base address of the code that may later be overwritten.
pub code_start: *mut u8,
// Length of the default code, excluding NOPs if the altcode sequence is longer.
pub origcode_len: usize,
// Actual length of the overwritable code, i.e. max(origcode_len, altcode_len).
pub padded_len: usize,
pub _rsvd: usize,
// These two fields point to the alternative code (in .rodata), and possible new nop bytes,
// that will replace the code_start..+padded_len
pub altcode_start: *const u8,
pub altcode_len: usize,
}
#[cold]
pub unsafe fn early_init(bsp: bool) {
unsafe {
let relocs_offset = crate::kernel_executable_offsets::__altrelocs_start();
// __altrelocs_end > __altrelocs_start so this cannot overflow
#[expect(clippy::arithmetic_side_effects)]
let relocs_size = crate::kernel_executable_offsets::__altrelocs_end() - relocs_offset;
// AltReloc is not a ZST so the modulo and division will never panic
#[expect(clippy::arithmetic_side_effects)]
{
assert_eq!(relocs_size % size_of::<AltReloc>(), 0)
}
#[expect(clippy::arithmetic_side_effects)]
let relocs = core::slice::from_raw_parts(
relocs_offset as *const AltReloc,
relocs_size / size_of::<AltReloc>(),
);
let mut enable = KcpuFeatures::empty();
if cfg!(not(cpu_feature_never = "smap")) && has_ext_feat(|feat| feat.has_smap()) {
// SMAP (Supervisor-Mode Access Prevention) forbids the kernel from accessing any
// userspace-accessible pages, with the necessary exception of when RFLAGS.AC = 1. This
// limits user-memory accesses to the UserSlice wrapper, so that no data outside of
// usercopy functions can be accidentally accessed by the kernel.
x86::controlregs::cr4_write(x86::controlregs::cr4() | Cr4::CR4_ENABLE_SMAP);
// Clear CLAC in (the probably unlikely) case the bootloader set it earlier.
x86::bits64::rflags::clac();
enable |= KcpuFeatures::SMAP;
} else {
assert!(cfg!(not(cpu_feature_always = "smap")));
}
if cfg!(not(cpu_feature_never = "fsgsbase"))
&& let Some(f) = cpuid().get_extended_feature_info()
&& f.has_fsgsbase()
{
x86::controlregs::cr4_write(
x86::controlregs::cr4() | x86::controlregs::Cr4::CR4_ENABLE_FSGSBASE,
);
enable |= KcpuFeatures::FSGSBASE;
} else {
assert!(cfg!(not(cpu_feature_always = "fsgsbase")));
}
#[cfg(not(cpu_feature_never = "xsave"))]
if feature_info().has_xsave() {
use raw_cpuid::{ExtendedRegisterStateLocation, ExtendedRegisterType};
x86::controlregs::cr4_write(
x86::controlregs::cr4() | x86::controlregs::Cr4::CR4_ENABLE_OS_XSAVE,
);
let mut xcr0 = Xcr0::XCR0_FPU_MMX_STATE | Xcr0::XCR0_SSE_STATE;
x86::controlregs::xcr0_write(xcr0);
let ext_state_info = cpuid()
.get_extended_state_info()
.expect("must be present if XSAVE is supported");
enable |= KcpuFeatures::XSAVE;
enable.set(KcpuFeatures::XSAVEOPT, ext_state_info.has_xsaveopt());
let info = xsave::XsaveInfo {
ymm_upper_offset: feature_info().has_avx().then(|| {
xcr0 |= Xcr0::XCR0_AVX_STATE;
x86::controlregs::xcr0_write(xcr0);
let state = ext_state_info
.iter()
.find(|state| {
state.register() == ExtendedRegisterType::Avx
&& state.location() == ExtendedRegisterStateLocation::Xcr0
})
.expect("CPUID said AVX was supported but there's no state info");
// 16 * size_of::<u128>() is well below usize::MAX
#[expect(clippy::arithmetic_side_effects)]
if state.size() as usize != 16 * size_of::<u128>() {
warn!("Unusual AVX state size {}", state.size());
}
state.offset()
}),
xsave_size: ext_state_info.xsave_area_size_enabled_features(),
};
debug!("XSAVE: {:?}", info);
xsave::XSAVE_INFO.call_once(|| info);
} else {
assert!(cfg!(not(cpu_feature_always = "xsave")));
}
if !bsp {
return;
}
overwrite(relocs, enable);
if cfg!(not(feature = "self_modifying")) {
assert!(
cfg!(not(cpu_feature_auto = "smap"))
&& cfg!(not(cpu_feature_auto = "fsgsbase"))
&& cfg!(not(cpu_feature_auto = "xsave"))
&& cfg!(not(cpu_feature_auto = "xsaveopt"))
);
}
FEATURES.call_once(|| enable);
}
}
unsafe fn overwrite(relocs: &[AltReloc], enable: KcpuFeatures) {
unsafe {
if cfg!(not(feature = "self_modifying")) {
return;
}
debug!("self-modifying features: {:?}", enable);
let mut mapper = KernelMapper::lock_rw();
for reloc in relocs.iter().copied() {
let name = core::str::from_utf8(core::slice::from_raw_parts(
reloc.name_start,
reloc.name_len,
))
.expect("invalid feature name");
let altcode = core::slice::from_raw_parts(reloc.altcode_start, reloc.altcode_len);
let dst_pages = PageSpan::between(
Page::containing_address(VirtualAddress::new(reloc.code_start as usize)),
Page::containing_address(VirtualAddress::new(
(reloc.code_start as usize + reloc.padded_len).next_multiple_of(PAGE_SIZE),
)),
);
for page in dst_pages.pages() {
mapper
.remap(
page.start_address(),
PageFlags::new().write(true).execute(true).global(true),
)
.unwrap()
.flush();
}
let code = core::slice::from_raw_parts_mut(reloc.code_start, reloc.padded_len);
trace!(
"feature {} current {:x?} altcode {:x?}",
name,
code,
altcode
);
let feature_is_enabled = match name {
"smap" => enable.contains(KcpuFeatures::SMAP),
"fsgsbase" => enable.contains(KcpuFeatures::FSGSBASE),
"xsave" => enable.contains(KcpuFeatures::XSAVE),
"xsaveopt" => enable.contains(KcpuFeatures::XSAVEOPT),
//_ => panic!("unknown altcode relocation: {}", name),
_ => true,
};
// XXX: The `.nops` directive only works for constant lengths, and the variable `.skip -X`
// only outputs the (slower) single-byte 0x90 NOP.
// This table is from the "Software Optimization Guide for AMD Family 19h Processors" (November
// 2020).
const NOPS_TABLE: [&[u8]; 11] = [
&[0x90],
&[0x66, 0x90],
&[0x0f, 0x1f, 0x00],
&[0x0f, 0x1f, 0x40, 0x00],
&[0x0f, 0x1f, 0x44, 0x00, 0x00],
&[0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00],
&[0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00],
&[0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
&[0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
&[0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
&[
0x66, 0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
],
];
if feature_is_enabled {
trace!("feature {} origcode {:x?}", name, code);
let (dst, dst_nops) = code.split_at_mut(altcode.len());
dst.copy_from_slice(altcode);
for chunk in dst_nops.chunks_mut(NOPS_TABLE.len()) {
// `chunk.len() - 1` is always in bounds because we are chunking by
// `NOPS_TABLE.len()`
#[expect(clippy::indexing_slicing)]
// `chunk.len()` will never be 0
#[expect(clippy::arithmetic_side_effects)]
chunk.copy_from_slice(NOPS_TABLE[chunk.len() - 1]);
}
trace!("feature {} new {:x?} altcode {:x?}", name, code, altcode);
} else {
trace!("feature !{} origcode {:x?}", name, code);
let (_, padded) = code.split_at_mut(reloc.origcode_len);
// Not strictly necessary, but reduces the number of instructions using longer nop
// instructions.
for chunk in padded.chunks_mut(NOPS_TABLE.len()) {
// `chunk.len() - 1` is always in bounds because we are chunking by
// `NOPS_TABLE.len()`
#[expect(clippy::indexing_slicing)]
// `chunk.len()` will never be 0
#[expect(clippy::arithmetic_side_effects)]
chunk.copy_from_slice(NOPS_TABLE[chunk.len() - 1]);
}
trace!("feature !{} new {:x?}", name, code);
}
for page in dst_pages.pages() {
mapper
.remap(
page.start_address(),
PageFlags::new().write(false).execute(true).global(true),
)
.unwrap()
.flush();
}
}
}
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct KcpuFeatures: usize {
const SMAP = 1;
const FSGSBASE = 2;
const XSAVE = 4;
const XSAVEOPT = 8;
}
}
static FEATURES: Once<KcpuFeatures> = Once::new();
pub fn features() -> KcpuFeatures {
*FEATURES.get().expect("early_cpu_init was not called")
}
#[cfg(not(cpu_feature_never = "xsave"))]
mod xsave {
use spin::Once;
#[derive(Debug)]
pub struct XsaveInfo {
pub ymm_upper_offset: Option<u32>,
pub xsave_size: u32,
}
pub(super) static XSAVE_INFO: Once<XsaveInfo> = Once::new();
pub fn info() -> Option<&'static XsaveInfo> {
XSAVE_INFO.get()
}
}
pub fn kfx_size() -> usize {
#[cfg(not(cpu_feature_never = "xsave"))]
{
// This wont overflow
#[expect(clippy::arithmetic_side_effects)]
match xsave::info() {
Some(info) => FXSAVE_SIZE + XSAVE_HEADER_SIZE + info.xsave_size as usize,
None => FXSAVE_SIZE,
}
}
#[cfg(cpu_feature_never = "xsave")]
{
// FXSAVE size
FXSAVE_SIZE
}
}
pub const FXSAVE_SIZE: usize = 512;
pub const XSAVE_HEADER_SIZE: usize = 64;
+26
View File
@@ -0,0 +1,26 @@
// Because the memory map is so important to not be aliased, it is defined here, in one place.
//
// - The lower half (256 PML4 entries; 128 TiB) is reserved for userspace. These mappings are
// associated with _address spaces_, and change when context switching, unless the address spaces
// match.
// - The upper half is reserved for the kernel. Kernel mappings are preserved across context
// switches.
//
// Each PML4 entry references 512 GiB of virtual memory.
/// The size of a single PML4
pub const PML4_SIZE: usize = 0x0000_0080_0000_0000;
/// Offset of kernel
const KERNEL_OFFSET: usize = (1_usize << 31).wrapping_neg();
/// Offset to kernel heap
#[inline(always)]
pub fn kernel_heap_offset() -> usize {
crate::kernel_executable_offsets::KERNEL_OFFSET() - PML4_SIZE
}
/// End offset of the user image, i.e. kernel start
// TODO: Make this offset at least PAGE_SIZE less? There are known hardware bugs on some arches,
// for example on x86 if instructions execute near the 48-bit canonical address boundary.
pub const USER_END_OFFSET: usize = 256 * PML4_SIZE;
+532
View File
@@ -0,0 +1,532 @@
use crate::{arch::flags::FLAG_SINGLESTEP, memory::ArchIntCtx, panic, syscall::IntRegisters};
#[derive(Default)]
#[repr(C)]
pub struct ScratchRegisters {
pub r11: usize,
pub r10: usize,
pub r9: usize,
pub r8: usize,
pub rsi: usize,
pub rdi: usize,
pub rdx: usize,
pub rcx: usize,
pub rax: usize,
}
impl ScratchRegisters {
pub fn dump(&self) {
println!("RAX: {:016x}", { self.rax });
println!("RCX: {:016x}", { self.rcx });
println!("RDX: {:016x}", { self.rdx });
println!("RDI: {:016x}", { self.rdi });
println!("RSI: {:016x}", { self.rsi });
println!("R8: {:016x}", { self.r8 });
println!("R9: {:016x}", { self.r9 });
println!("R10: {:016x}", { self.r10 });
println!("R11: {:016x}", { self.r11 });
}
}
#[derive(Default)]
#[repr(C)]
pub struct PreservedRegisters {
pub r15: usize,
pub r14: usize,
pub r13: usize,
pub r12: usize,
pub rbp: usize,
pub rbx: usize,
}
impl PreservedRegisters {
pub fn dump(&self) {
println!("RBX: {:016x}", { self.rbx });
println!("RBP: {:016x}", { self.rbp });
println!("R12: {:016x}", { self.r12 });
println!("R13: {:016x}", { self.r13 });
println!("R14: {:016x}", { self.r14 });
println!("R15: {:016x}", { self.r15 });
}
}
#[derive(Default)]
#[repr(C)]
pub struct IretRegisters {
pub rip: usize,
pub cs: usize,
pub rflags: usize,
// In x86 Protected Mode, i.e. 32-bit kernels, the following two registers are conditionally
// pushed if the privilege ring changes. In x86 Long Mode however, i.e. 64-bit kernels, they
// are unconditionally pushed, mostly due to stack alignment requirements.
pub rsp: usize,
pub ss: usize,
}
impl IretRegisters {
pub fn dump(&self) {
println!("RFLAG: {:016x}", { self.rflags });
println!("CS: {:016x}", { self.cs });
println!("RIP: {:016x}", { self.rip });
println!("RSP: {:016x}", { self.rsp });
println!("SS: {:016x}", { self.ss });
unsafe {
let fsbase = x86::msr::rdmsr(x86::msr::IA32_FS_BASE);
let gsbase = x86::msr::rdmsr(x86::msr::IA32_KERNEL_GSBASE);
let kgsbase = x86::msr::rdmsr(x86::msr::IA32_GS_BASE);
println!(
"FSBASE {:016x}\nGSBASE {:016x}\nKGSBASE {:016x}",
fsbase, gsbase, kgsbase
);
}
}
}
#[derive(Default)]
#[repr(C)]
pub struct InterruptStack {
pub preserved: PreservedRegisters,
pub scratch: ScratchRegisters,
pub iret: IretRegisters,
}
impl InterruptStack {
pub fn init(&mut self) {
// Always enable interrupts!
self.iret.rflags = x86::bits64::rflags::RFlags::FLAGS_IF.bits() as usize;
self.iret.cs = (crate::arch::gdt::GDT_USER_CODE << 3) | 3;
self.iret.ss = (crate::arch::gdt::GDT_USER_DATA << 3) | 3;
}
pub fn frame_pointer(&self) -> usize {
self.preserved.rbp
}
pub fn stack_pointer(&self) -> usize {
self.iret.rsp
}
pub fn set_stack_pointer(&mut self, rsp: usize) {
self.iret.rsp = rsp;
}
pub fn instr_pointer(&self) -> usize {
self.iret.rip
}
pub fn sig_archdep_reg(&self) -> usize {
self.iret.rflags
}
pub fn set_instr_pointer(&mut self, rip: usize) {
self.iret.rip = rip;
}
pub fn set_arg1(&mut self, arg_opt: Option<usize>) {
if let Some(arg) = arg_opt {
self.scratch.rsi = arg;
}
}
pub fn dump(&self) {
self.iret.dump();
self.scratch.dump();
self.preserved.dump();
}
pub fn trace(&self) {
self.dump();
unsafe {
panic::user_stack_trace(self);
panic::stack_trace();
}
}
/// Saves all registers to a struct used by the proc:
/// scheme to read/write registers.
pub fn save(&self, all: &mut IntRegisters) {
all.r15 = self.preserved.r15;
all.r14 = self.preserved.r14;
all.r13 = self.preserved.r13;
all.r12 = self.preserved.r12;
all.rbp = self.preserved.rbp;
all.rbx = self.preserved.rbx;
all.r11 = self.scratch.r11;
all.r10 = self.scratch.r10;
all.r9 = self.scratch.r9;
all.r8 = self.scratch.r8;
all.rsi = self.scratch.rsi;
all.rdi = self.scratch.rdi;
all.rdx = self.scratch.rdx;
all.rcx = self.scratch.rcx;
all.rax = self.scratch.rax;
all.rip = self.iret.rip;
all.cs = self.iret.cs;
all.rflags = self.iret.rflags;
all.rsp = self.iret.rsp;
all.ss = self.iret.ss;
}
/// Loads all registers from a struct used by the proc:
/// scheme to read/write registers.
pub fn load(&mut self, all: &IntRegisters) {
self.preserved.r15 = all.r15;
self.preserved.r14 = all.r14;
self.preserved.r13 = all.r13;
self.preserved.r12 = all.r12;
self.preserved.rbp = all.rbp;
self.preserved.rbx = all.rbx;
self.scratch.r11 = all.r11;
self.scratch.r10 = all.r10;
self.scratch.r9 = all.r9;
self.scratch.r8 = all.r8;
self.scratch.rsi = all.rsi;
self.scratch.rdi = all.rdi;
self.scratch.rdx = all.rdx;
self.scratch.rcx = all.rcx;
self.scratch.rax = all.rax;
self.iret.rip = all.rip;
self.iret.rsp = all.rsp;
// CS and SS are immutable, at least their privilege levels.
// OF, DF, 0, TF => D
// SF, ZF, 0, AF => D
// 0, PF, 1, CF => 5
const ALLOWED_RFLAGS: usize = 0xDD5;
self.iret.rflags &= !ALLOWED_RFLAGS;
self.iret.rflags |= all.rflags & ALLOWED_RFLAGS;
}
/// Enables the "Trap Flag" in the FLAGS register, causing the CPU
/// to send a Debug exception after the next instruction. This is
/// used for singlestep in the proc: scheme.
pub fn set_singlestep(&mut self, enabled: bool) {
if enabled {
self.iret.rflags |= FLAG_SINGLESTEP;
} else {
self.iret.rflags &= !FLAG_SINGLESTEP;
}
}
}
#[macro_export]
macro_rules! push_scratch {
() => {
"
// Push scratch registers
push rcx
push rdx
push rdi
push rsi
push r8
push r9
push r10
push r11
"
};
}
#[macro_export]
macro_rules! pop_scratch {
() => {
"
// Pop scratch registers
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi
pop rdx
pop rcx
pop rax
"
};
}
#[macro_export]
macro_rules! push_preserved {
() => {
"
// Push preserved registers
push rbx
push rbp
push r12
push r13
push r14
push r15
"
};
}
#[macro_export]
macro_rules! pop_preserved {
() => {
"
// Pop preserved registers
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
"
};
}
macro_rules! swapgs_iff_ring3_fast {
// TODO: Spectre V1: LFENCE?
() => {
"
// Check whether the last two bits RSP+8 (code segment) are equal to zero.
test QWORD PTR [rsp + 8], 0x3
// Skip the SWAPGS instruction if CS & 0b11 == 0b00.
jz 2f
swapgs
2:
"
};
}
macro_rules! swapgs_iff_ring3_fast_errorcode {
// TODO: Spectre V1: LFENCE?
() => {
"
test QWORD PTR [rsp + 16], 0x3
jz 2f
swapgs
2:
"
};
}
macro_rules! conditional_swapgs_paranoid {
// For regular interrupt handlers and the syscall handler, managing IA32_GS_BASE and
// IA32_KERNEL_GS_BASE (the "GSBASE registers") is more or less trivial when using the SWAPGS
// instruction.
//
// The syscall handler simply runs SWAPGS, as syscalls can only originate from usermode,
// whereas interrupt handlers conditionally SWAPGS unless the interrupt was triggered from
// kernel mode, in which case the "swap state" is already valid, and there is no need to
// SWAPGS.
//
// Handling GSBASE correctly for paranoid interrupts however, is not as simple. NMIs can occur
// between the check of whether an interrupt came from usermode, and the actual SWAPGS
// instruction. #DB can also be triggered inside of a kernel interrupt handler, due to
// breakpoints, even though setting up such breakpoints in the first place, is not yet
// supported by the kernel.
//
// Luckily, the GDT always resides in the PCR (at least after init_paging, but there are no
// interrupt handlers set up before that), allowing GSBASE to be calculated relatively cheaply.
// Out of the two GSBASE registers, at least one must be *the* kernel GSBASE, allowing for a
// simple conditional SWAPGS.
//
// (An alternative to conditionally executing SWAPGS, would be to save and restore GSBASE via
// e.g. the stack. That would nonetheless require saving and restoring both GSBASE registers,
// if the interrupt handler should be allowed to context switch, which the current #DB handler
// may do.)
//
// TODO: Handle nested NMIs like Linux does (https://lwn.net/Articles/484932/)?.
() => { concat!(
// Put the GDT base pointer in RDI.
"
sub rsp, 16
sgdt [rsp + 6]
mov rdi, [rsp + 8]
add rsp, 16
",
// Calculate the PCR address by subtracting the offset of the GDT in the PCR struct.
"sub rdi, {PCR_GDT_OFFSET};",
// Read the current IA32_GS_BASE value into RDX.
alternative!(
feature: "fsgsbase",
then: ["rdgsbase rdx"],
default: ["
mov ecx, {IA32_GS_BASE}
rdmsr
shl rdx, 32
or rdx, rax
"]
),
// If they were not equal, the PCR address must instead be in IA32_KERNEL_GS_BASE,
// requiring a SWAPGS. GSBASE needs to be swapped back, so store the same flag in RBX.
// TODO: Spectre V1: LFENCE?
"
cmp rdx, rdi
sete bl
je 2f
swapgs
2:
",
) }
}
macro_rules! conditional_swapgs_back_paranoid {
() => {
"
test bl, bl
jnz 2f
swapgs
2:
"
};
}
macro_rules! nop {
() => {
"
// Unused: {IA32_GS_BASE} {PCR_GDT_OFFSET}
"
};
}
#[macro_export]
macro_rules! interrupt_stack {
// XXX: Apparently we cannot use $expr and check for bool exhaustiveness, so we will have to
// use idents directly instead.
($name:ident, $save1:ident!, $save2:ident!, $rstor2:ident!, $rstor1:ident!, is_paranoid: $is_paranoid:expr_2021, |$stack:ident| $code:block) => {
#[unsafe(naked)]
pub unsafe extern "C" fn $name() {
unsafe extern "C" fn inner($stack: &mut $crate::arch::x86_64::interrupt::InterruptStack) {
$code
}
core::arch::naked_asm!(
// Clear direction flag, required by ABI when running any Rust code in the kernel.
"cld;",
// Backup all userspace registers to stack
$save1!(),
"push rax",
push_scratch!(),
push_preserved!(),
$save2!(),
// TODO: Map PTI
// $crate::arch::x86_64::pti::map();
// Call inner function with pointer to stack
"
mov rdi, rsp
call {inner}
",
// TODO: Unmap PTI
// $crate::arch::x86_64::pti::unmap();
$rstor2!(),
// Restore all userspace registers
pop_preserved!(),
pop_scratch!(),
$rstor1!(),
"iretq",
inner = sym inner,
IA32_GS_BASE = const(x86::msr::IA32_GS_BASE),
PCR_GDT_OFFSET = const(core::mem::offset_of!($crate::arch::gdt::ProcessorControlRegion, gdt)),
);
}
};
($name:ident, |$stack:ident| $code:block) => { interrupt_stack!($name, swapgs_iff_ring3_fast!, nop!, nop!, swapgs_iff_ring3_fast!, is_paranoid: false, |$stack| $code); };
($name:ident, @paranoid, |$stack:ident| $code:block) => { interrupt_stack!($name, nop!, conditional_swapgs_paranoid!, conditional_swapgs_back_paranoid!, nop!, is_paranoid: true, |$stack| $code); }
}
#[macro_export]
macro_rules! interrupt {
($name:ident, || $code:block) => {
#[unsafe(naked)]
pub unsafe extern "C" fn $name() {
unsafe extern "C" fn inner() {
$code
}
core::arch::naked_asm!(
// Clear direction flag, required by ABI when running any Rust code in the kernel.
"cld;",
// Backup all userspace registers to stack
swapgs_iff_ring3_fast!(),
"push rax",
push_scratch!(),
// TODO: Map PTI
// $crate::arch::x86_64::pti::map();
// Call inner function with pointer to stack
"call {inner}",
// TODO: Unmap PTI
// $crate::arch::x86_64::pti::unmap();
// Restore all userspace registers
pop_scratch!(),
swapgs_iff_ring3_fast!(),
"iretq",
inner = sym inner,
);
}
};
}
#[macro_export]
macro_rules! interrupt_error {
($name:ident, |$stack:ident, $error_code:ident| $code:block) => {
#[unsafe(naked)]
pub unsafe extern "C" fn $name() {
unsafe extern "C" fn inner($stack: &mut $crate::arch::x86_64::interrupt::handler::InterruptStack, $error_code: usize) {
$code
}
core::arch::naked_asm!(
// Clear direction flag, required by ABI when running any Rust code in the kernel.
"cld;",
swapgs_iff_ring3_fast_errorcode!(),
// Don't push RAX yet, as the error code is already stored in RAX's position.
// Push all userspace registers
push_scratch!(),
push_preserved!(),
// Now that we have a couple of usable registers, put the error code in the second
// argument register for the inner function, and save RAX where it would normally
// be.
"mov rsi, [rsp + {rax_offset}];",
"mov [rsp + {rax_offset}], rax;",
// TODO: Map PTI
// $crate::arch::x86_64::pti::map();
// Call inner function with pointer to stack, and error code.
"mov rdi, rsp;",
"call {inner};",
// TODO: Unmap PTI
// $crate::arch::x86_64::pti::unmap();
// Restore all userspace registers
pop_preserved!(),
pop_scratch!(),
// The error code has already been popped, so use the regular macro.
swapgs_iff_ring3_fast!(),
"iretq;",
inner = sym inner,
rax_offset = const(size_of::<$crate::arch::interrupt::handler::PreservedRegisters>() + size_of::<$crate::arch::interrupt::handler::ScratchRegisters>() - 8),
);
}
};
}
impl ArchIntCtx for InterruptStack {
fn ip(&self) -> usize {
self.iret.rip
}
fn recover_and_efault(&mut self) {
// We were inside a usercopy function that failed. This is handled by setting rax to a
// nonzero value, and emulating the ret instruction.
self.scratch.rax = 1;
let ret_addr = unsafe { (self.iret.rsp as *const usize).read() };
self.iret.rsp += 8;
self.iret.rip = ret_addr;
self.iret.rflags &= !(1 << 18);
}
}
+10
View File
@@ -0,0 +1,10 @@
//! Interrupt instructions
pub use crate::arch::x86_shared::interrupt::*;
#[macro_use]
pub mod handler;
pub mod syscall;
pub use self::handler::InterruptStack;
+195
View File
@@ -0,0 +1,195 @@
use crate::{
arch::{gdt, interrupt::InterruptStack},
ptrace,
sync::CleanLockToken,
syscall,
syscall::flag::{PTRACE_FLAG_IGNORE, PTRACE_STOP_POST_SYSCALL, PTRACE_STOP_PRE_SYSCALL},
};
use core::mem::offset_of;
use x86::{
bits64::{rflags::RFlags, task::TaskStateSegment},
msr,
segmentation::SegmentSelector,
};
pub unsafe fn init() {
unsafe {
// IA32_STAR[31:0] are reserved.
// The base selector of the two consecutive segments for kernel code and the immediately
// succeeding stack (data).
let syscall_cs_ss_base = (gdt::GDT_KERNEL_CODE as u16) << 3;
// The base selector of the three consecutive segments (of which two are used) for user code
// and user data. It points to a 32-bit code segment, which must be followed by a data segment
// (stack), and a 64-bit code segment.
let sysret_cs_ss_base = ((gdt::GDT_USER_CODE32_UNUSED as u16) << 3) | 3;
let star_high = u32::from(syscall_cs_ss_base) | (u32::from(sysret_cs_ss_base) << 16);
msr::wrmsr(msr::IA32_STAR, u64::from(star_high) << 32);
#[expect(clippy::fn_to_numeric_cast)]
msr::wrmsr(msr::IA32_LSTAR, syscall_instruction as u64);
// DF needs to be cleared, required by the compiler ABI. If DF were not part of FMASK,
// userspace would be able to reverse the direction of in-kernel REP MOVS/STOS/(CMPS/SCAS), and
// cause all sorts of memory corruption.
//
// IF needs to be cleared, as the kernel currently assumes interrupts are disabled except in
// usermode and in kmain.
//
// TF needs to be cleared, as enabling userspace-rflags-controlled singlestep in the kernel
// would be a bad idea.
//
// AC it should always be cleared when entering the kernel (and never be set except in usercopy
// functions), if for some reason AC was set before entering userspace (AC can only be modified
// by kernel code).
//
// The other flags could indeed be preserved and excluded from FMASK, but since they are not
// used to pass data to the kernel, they might as well be masked with *marginal* security
// benefits.
//
// Flags not included here are IOPL (not relevant to the kernel at all), "CPUID flag" (not used
// at all in 64-bit mode), RF (not used yet, but DR breakpoints would remain enabled both in
// user and kernel mode), VM8086 (not used at all), and VIF/VIP (system-level status flags?).
let mask_critical =
RFlags::FLAGS_DF | RFlags::FLAGS_IF | RFlags::FLAGS_TF | RFlags::FLAGS_AC;
let mask_other = RFlags::FLAGS_CF
| RFlags::FLAGS_PF
| RFlags::FLAGS_AF
| RFlags::FLAGS_ZF
| RFlags::FLAGS_SF
| RFlags::FLAGS_OF;
msr::wrmsr(msr::IA32_FMASK, (mask_critical | mask_other).bits());
let efer = msr::rdmsr(msr::IA32_EFER);
msr::wrmsr(msr::IA32_EFER, efer | 1);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn __inner_syscall_instruction(stack: *mut InterruptStack) {
unsafe {
let mut token = CleanLockToken::new();
let allowed = ptrace::breakpoint_callback(PTRACE_STOP_PRE_SYSCALL, None, &mut token)
.and_then(|_| ptrace::next_breakpoint().map(|f| !f.contains(PTRACE_FLAG_IGNORE)));
if allowed.unwrap_or(true) {
let scratch = &(*stack).scratch;
let ret = syscall::syscall(
scratch.rax,
scratch.rdi,
scratch.rsi,
scratch.rdx,
scratch.r10,
scratch.r8,
scratch.r9,
&mut token,
);
(*stack).scratch.rax = ret;
}
ptrace::breakpoint_callback(PTRACE_STOP_POST_SYSCALL, None, &mut token);
}
}
#[unsafe(naked)]
pub unsafe extern "C" fn syscall_instruction() {
core::arch::naked_asm!(
// Yes, this is magic. No, you don't need to understand
"swapgs;", // Swap KGSBASE with GSBASE, allowing fast TSS access.
"mov gs:[{sp}], rsp;", // Save userspace stack pointer
"mov rsp, gs:[{ksp}];", // Load kernel stack pointer
"push QWORD PTR {ss_sel};", // Push fake userspace SS (resembling iret frame)
"push QWORD PTR gs:[{sp}];", // Push userspace rsp
"push r11;", // Push rflags
"push QWORD PTR {cs_sel};", // Push fake CS (resembling iret stack frame)
"push rcx;", // Push userspace return pointer
// Push context registers
"push rax;",
push_scratch!(),
push_preserved!(),
// TODO: Map PTI
// $crate::arch::x86_64::pti::map();
// Call inner funtion
"mov rdi, rsp;",
"call __inner_syscall_instruction;",
// TODO: Unmap PTI
// $crate::arch::x86_64::pti::unmap();
"
.globl enter_usermode
enter_usermode:
",
// Pop context registers
pop_preserved!(),
pop_scratch!(),
// Restore user GSBASE by swapping GSBASE and KGSBASE.
"swapgs;",
// TODO: Should we unconditionally jump or avoid jumping, to hint to the branch predictor that
// singlestep is NOT set?
//
// It appears Intel CPUs assume (previously unknown) forward conditional branches to not be
// taken, and AMD appears to assume all previously unknown conditional branches will not be
// taken.
// Check if the Trap Flag (singlestep flag) is set. If so, sysretq will return to before the
// instruction, whereas debuggers expect the iretq behavior of returning to after the
// instruction.
"test BYTE PTR [rsp + 17], 1;",
// If set, return using IRETQ instead.
"jnz 2f;",
// Otherwise, continue with the fast sysretq.
// Pop userspace return pointer
"pop rcx;",
// We must ensure RCX is canonical; if it is not when running sysretq, the consequences can be
// fatal from a security perspective.
//
// See https://xenproject.org/2012/06/13/the-intel-sysret-privilege-escalation/.
//
// This is not just theoretical; ptrace allows userspace to change RCX (via RIP) of target
// processes.
//
// While we could also conditionally IRETQ here, an easier method is to simply sign-extend RCX:
// Shift away the upper 16 bits (0xBAAD_8000_DEAD_BEEF => 0x8000_DEAD_BEEF_XXXX).
"shl rcx, 16;",
// Shift arithmetically right by 16 bits, effectively extending the 47th sign bit to bits
// 63:48 (0x8000_DEAD_BEEF_XXXX => 0xFFFF_8000_DEAD_BEEF).
"sar rcx, 16;",
"add rsp, 8;", // Pop fake userspace CS
"pop r11;", // Pop rflags
"pop rsp;", // Restore userspace stack pointer
"sysretq;", // Return into userspace; RCX=>RIP,R11=>RFLAGS
// IRETQ fallback:
"
.p2align 4
2:
xor rcx, rcx
xor r11, r11
iretq
",
sp = const(offset_of!(gdt::ProcessorControlRegion, user_rsp_tmp)),
ksp = const(offset_of!(gdt::ProcessorControlRegion, tss) + offset_of!(TaskStateSegment, rsp)),
ss_sel = const(SegmentSelector::new(gdt::GDT_USER_DATA as u16, x86::Ring::Ring3).bits()),
cs_sel = const(SegmentSelector::new(gdt::GDT_USER_CODE as u16, x86::Ring::Ring3).bits()),
);
}
unsafe extern "C" {
// TODO: macro?
pub fn enter_usermode();
}
+80
View File
@@ -0,0 +1,80 @@
macro_rules! expand_bool(
($value:expr_2021) => {
concat!($value)
}
);
macro_rules! alternative(
(feature: $feature:literal, then: [$($then:expr_2021),*], default: [$($default:expr_2021),*]) => {
alternative2!(feature1: $feature, then1: [$($then),*], feature2: "", then2: [""], default: [$($default),*])
}
);
macro_rules! saturating_sub(
($lhs:literal, $rhs:literal) => { concat!(
"((", $lhs, ")>(", $rhs, "))*((", $lhs, ")-(", $rhs, "))",
) }
);
// Use feature1 if present, otherwise try using feature2, otherwise use default.
//
// cpu_feature_always simply means it is always enabled. Thus, if feature2, which has lower
// priority, is "always" but feature1 is "auto", feature2 will still be checked for, and feature2
// will become the fallback code.
//
// An empty string as feature is equivalent with "never".
macro_rules! alternative2(
(feature1: $feature1:literal, then1: [$($then1:expr_2021),*], feature2: $feature2:literal, then2: [$($then2:expr_2021),*], default: [$($default:expr_2021),*]) => {
concat!("
.set true, 1
.set false, 0
40:
.if ", expand_bool!(cfg!(cpu_feature_always = $feature1)), "
", $($then1,)* "
.elseif ", expand_bool!(cfg!(cpu_feature_always = $feature2)), "
", $($then2,)* "
.else
", $($default,)* "
.endif
42:
.if ", expand_bool!(cfg!(cpu_feature_auto = $feature1)), "
.skip -", saturating_sub!("51f - 50f", "42b - 40b"), ", 0x90
.endif
.if ", expand_bool!(cfg!(cpu_feature_auto = $feature2)), "
.skip -", saturating_sub!("61f - 60f", "42b - 40b"), ", 0x90
.endif
41:
",
// FIXME: The assembler apparently complains "invalid number of bytes" despite it being
// quite obvious what saturating_sub does.
// Declare them in reverse order. Last relocation wins!
alternative_auto!("6", $feature2, [$($then2),*]),
alternative_auto!("5", $feature1, [$($then1),*]),
)
};
);
macro_rules! alternative_auto(
($first_digit:literal, $feature:literal, [$($then:expr_2021),*]) => { concat!(
".if ", expand_bool!(cfg!(cpu_feature_auto = $feature)), "
.pushsection .altcode.", $feature, ",\"a\"
", $first_digit, "0:
", $($then,)* "
", $first_digit, "1:
.popsection
.pushsection .altfeatures.", $feature, ",\"a\"
70: .ascii \"", $feature, "\"
71:
.popsection
.pushsection .altrelocs.", $feature, ",\"a\"
.quad 70b
.quad 71b - 70b
.quad 40b
.quad 42b - 40b
.quad 41b - 40b
.quad 0
.quad ", $first_digit, "0b
.quad ", $first_digit, "1b - ", $first_digit, "0b
.popsection
.endif
",
) }
);
+29
View File
@@ -0,0 +1,29 @@
use x86::controlregs::Cr4;
use crate::{
arch::cpuid::{cpuid, has_ext_feat},
cpu_set::LogicalCpuId,
};
pub unsafe fn init(cpu_id: LogicalCpuId) {
unsafe {
if has_ext_feat(|feat| feat.has_umip()) {
// UMIP (UserMode Instruction Prevention) forbids userspace from calling SGDT, SIDT, SLDT,
// SMSW and STR. KASLR is currently not implemented, but this protects against leaking
// addresses.
x86::controlregs::cr4_write(x86::controlregs::cr4() | Cr4::CR4_ENABLE_UMIP);
}
if has_ext_feat(|feat| feat.has_smep()) {
// SMEP (Supervisor-Mode Execution Prevention) forbids the kernel from executing
// instruction on any page marked "userspace-accessible". This improves security for
// obvious reasons.
x86::controlregs::cr4_write(x86::controlregs::cr4() | Cr4::CR4_ENABLE_SMEP);
}
if let Some(feats) = cpuid().get_extended_processor_and_feature_identifiers()
&& feats.has_rdtscp()
{
x86::msr::wrmsr(x86::msr::IA32_TSC_AUX, cpu_id.get().into());
}
}
}
+50
View File
@@ -0,0 +1,50 @@
pub use crate::arch::x86_shared::*;
pub mod alternative;
#[macro_use]
pub mod macros;
/// Constants like memory locations
pub mod consts;
/// Interrupt instructions
#[macro_use]
pub mod interrupt;
/// Miscellaneous processor features
pub mod misc;
// TODO: Maybe support rewriting relocations (using LD's --emit-relocs) when working with entire
// functions?
#[unsafe(naked)]
pub unsafe extern "C" fn arch_copy_to_user(dst: usize, src: usize, len: usize) -> u8 {
// TODO: spectre_v1
core::arch::naked_asm!(
".global __usercopy_start
__usercopy_start:",
alternative!(
feature: "smap",
then: ["
xor eax, eax
mov rcx, rdx
stac
rep movsb
clac
ret
"],
default: ["
xor eax, eax
mov rcx, rdx
rep movsb
ret
"]
),
".global __usercopy_end
__usercopy_end:"
);
}
pub use arch_copy_to_user as arch_copy_from_user;
pub use alternative::kfx_size;
+29
View File
@@ -0,0 +1,29 @@
use raw_cpuid::{CpuId, CpuIdResult, ExtendedFeatures, FeatureInfo};
pub fn cpuid() -> CpuId {
// FIXME check for cpuid availability during early boot and error out if it doesn't exist.
CpuId::with_cpuid_fn(|a, c| {
#[cfg(target_arch = "x86")]
let result = unsafe { core::arch::x86::__cpuid_count(a, c) };
#[cfg(target_arch = "x86_64")]
let result = unsafe { core::arch::x86_64::__cpuid_count(a, c) };
CpuIdResult {
eax: result.eax,
ebx: result.ebx,
ecx: result.ecx,
edx: result.edx,
}
})
}
#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
pub fn feature_info() -> FeatureInfo {
cpuid()
.get_feature_info()
.expect("x86_64 requires CPUID leaf=0x01 to be present")
}
#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
pub fn has_ext_feat(feat: impl FnOnce(ExtendedFeatures) -> bool) -> bool {
cpuid().get_extended_feature_info().is_some_and(feat)
}
+62
View File
@@ -0,0 +1,62 @@
#[cfg(feature = "qemu_debug")]
use spin::Mutex;
use spin::MutexGuard;
use crate::devices::serial::SerialKind;
#[cfg(feature = "lpss_debug")]
use crate::devices::uart_16550::SerialPort;
#[cfg(feature = "lpss_debug")]
use crate::syscall::io::Mmio;
#[cfg(feature = "qemu_debug")]
use crate::syscall::io::Pio;
#[cfg(feature = "qemu_debug")]
use syscall::io::Io;
use super::device::serial::{COM1, LPSS};
#[cfg(feature = "system76_ec_debug")]
use super::device::system76_ec::{System76Ec, SYSTEM76_EC};
#[cfg(feature = "qemu_debug")]
pub static QEMU: Mutex<Pio<u8>> = Mutex::new(Pio::<u8>::new(0x402));
pub struct Writer<'a> {
lpss: MutexGuard<'a, SerialKind>,
#[cfg(feature = "qemu_debug")]
qemu: MutexGuard<'a, Pio<u8>>,
serial: MutexGuard<'a, SerialKind>,
#[cfg(feature = "system76_ec_debug")]
system76_ec: MutexGuard<'a, Option<System76Ec>>,
}
impl<'a> Writer<'a> {
pub fn new() -> Writer<'a> {
Writer {
lpss: LPSS.lock(),
#[cfg(feature = "qemu_debug")]
qemu: QEMU.lock(),
serial: COM1.lock(),
#[cfg(feature = "system76_ec_debug")]
system76_ec: SYSTEM76_EC.lock(),
}
}
pub fn write(&mut self, buf: &[u8]) {
self.lpss.write(buf);
#[cfg(feature = "qemu_debug")]
{
for &b in buf {
self.qemu.write(b);
}
}
self.serial.write(buf);
#[cfg(feature = "system76_ec_debug")]
{
if let Some(ref mut system76_ec) = *self.system76_ec {
system76_ec.print_slice(buf);
}
}
}
}
+281
View File
@@ -0,0 +1,281 @@
use core::fmt::{Result, Write};
use crate::arch::cpuid::cpuid;
pub fn cpu_info<W: Write>(w: &mut W) -> Result {
let cpuid = cpuid();
if let Some(info) = cpuid.get_vendor_info() {
writeln!(w, "Vendor: {}", info.as_str())?;
}
if let Some(brand) = cpuid.get_processor_brand_string() {
writeln!(w, "Model: {}", brand.as_str())?;
}
if let Some(info) = cpuid.get_processor_frequency_info() {
writeln!(w, "CPU Base MHz: {}", info.processor_base_frequency())?;
writeln!(w, "CPU Max MHz: {}", info.processor_max_frequency())?;
writeln!(w, "Bus MHz: {}", info.bus_frequency())?;
}
write!(w, "Features:")?;
if let Some(info) = cpuid.get_feature_info() {
if info.has_fpu() {
write!(w, " fpu")?
};
if info.has_vme() {
write!(w, " vme")?
};
if info.has_de() {
write!(w, " de")?
};
if info.has_pse() {
write!(w, " pse")?
};
if info.has_tsc() {
write!(w, " tsc")?
};
if info.has_msr() {
write!(w, " msr")?
};
if info.has_pae() {
write!(w, " pae")?
};
if info.has_mce() {
write!(w, " mce")?
};
if info.has_cmpxchg8b() {
write!(w, " cx8")?
};
if info.has_apic() {
write!(w, " apic")?
};
if info.has_sysenter_sysexit() {
write!(w, " sep")?
};
if info.has_mtrr() {
write!(w, " mtrr")?
};
if info.has_pge() {
write!(w, " pge")?
};
if info.has_mca() {
write!(w, " mca")?
};
if info.has_cmov() {
write!(w, " cmov")?
};
if info.has_pat() {
write!(w, " pat")?
};
if info.has_pse36() {
write!(w, " pse36")?
};
if info.has_psn() {
write!(w, " psn")?
};
if info.has_clflush() {
write!(w, " clflush")?
};
if info.has_ds() {
write!(w, " ds")?
};
if info.has_acpi() {
write!(w, " acpi")?
};
if info.has_mmx() {
write!(w, " mmx")?
};
if info.has_fxsave_fxstor() {
write!(w, " fxsr")?
};
if info.has_sse() {
write!(w, " sse")?
};
if info.has_sse2() {
write!(w, " sse2")?
};
if info.has_ss() {
write!(w, " ss")?
};
if info.has_htt() {
write!(w, " ht")?
};
if info.has_tm() {
write!(w, " tm")?
};
if info.has_pbe() {
write!(w, " pbe")?
};
if info.has_sse3() {
write!(w, " sse3")?
};
if info.has_pclmulqdq() {
write!(w, " pclmulqdq")?
};
if info.has_ds_area() {
write!(w, " dtes64")?
};
if info.has_monitor_mwait() {
write!(w, " monitor")?
};
if info.has_cpl() {
write!(w, " ds_cpl")?
};
if info.has_vmx() {
write!(w, " vmx")?
};
if info.has_smx() {
write!(w, " smx")?
};
if info.has_eist() {
write!(w, " est")?
};
if info.has_tm2() {
write!(w, " tm2")?
};
if info.has_ssse3() {
write!(w, " ssse3")?
};
if info.has_cnxtid() {
write!(w, " cnxtid")?
};
if info.has_fma() {
write!(w, " fma")?
};
if info.has_cmpxchg16b() {
write!(w, " cx16")?
};
if info.has_pdcm() {
write!(w, " pdcm")?
};
if info.has_pcid() {
write!(w, " pcid")?
};
if info.has_dca() {
write!(w, " dca")?
};
if info.has_sse41() {
write!(w, " sse4_1")?
};
if info.has_sse42() {
write!(w, " sse4_2")?
};
if info.has_x2apic() {
write!(w, " x2apic")?
};
if info.has_movbe() {
write!(w, " movbe")?
};
if info.has_popcnt() {
write!(w, " popcnt")?
};
if info.has_tsc_deadline() {
write!(w, " tsc_deadline_timer")?
};
if info.has_aesni() {
write!(w, " aes")?
};
if info.has_xsave() {
write!(w, " xsave")?
};
if info.has_oxsave() {
write!(w, " xsaveopt")?
};
if info.has_avx() {
write!(w, " avx")?
};
if info.has_f16c() {
write!(w, " f16c")?
};
if info.has_rdrand() {
write!(w, " rdrand")?
};
}
if let Some(info) = cpuid.get_extended_processor_and_feature_identifiers() {
if info.has_64bit_mode() {
write!(w, " lm")?
};
if info.has_rdtscp() {
write!(w, " rdtscp")?
};
if info.has_1gib_pages() {
write!(w, " pdpe1gb")?
};
if info.has_execute_disable() {
write!(w, " nx")?
};
if info.has_syscall_sysret() {
write!(w, " syscall")?
};
if info.has_prefetchw() {
write!(w, " prefetchw")?
};
if info.has_lzcnt() {
write!(w, " lzcnt")?
};
if info.has_lahf_sahf() {
write!(w, " lahf_lm")?
};
}
if let Some(info) = cpuid.get_advanced_power_mgmt_info()
&& info.has_invariant_tsc()
{
write!(w, " constant_tsc")?
};
if let Some(info) = cpuid.get_extended_feature_info() {
if info.has_fsgsbase() {
write!(w, " fsgsbase")?
};
if info.has_tsc_adjust_msr() {
write!(w, " tsc_adjust")?
};
if info.has_bmi1() {
write!(w, " bmi1")?
};
if info.has_hle() {
write!(w, " hle")?
};
if info.has_avx2() {
write!(w, " avx2")?
};
if info.has_smep() {
write!(w, " smep")?
};
if info.has_bmi2() {
write!(w, " bmi2")?
};
if info.has_rep_movsb_stosb() {
write!(w, " erms")?
};
if info.has_invpcid() {
write!(w, " invpcid")?
};
if info.has_rtm() {
write!(w, " rtm")?
};
//if info.has_qm() { write!(w, " qm")? };
if info.has_fpu_cs_ds_deprecated() {
write!(w, " fpu_seg")?
};
if info.has_mpx() {
write!(w, " mpx")?
};
}
writeln!(w)?;
Ok(())
}
+125
View File
@@ -0,0 +1,125 @@
//! <https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/software-developers-hpet-spec-1-0a.pdf>
use super::pit;
use crate::acpi::hpet::Hpet;
use core::time::Duration;
const LEG_RT_CNF: u64 = 2;
const ENABLE_CNF: u64 = 1;
const TN_VAL_SET_CNF: u64 = 0x40;
const TN_TYPE_CNF: u64 = 0x08;
const TN_INT_ENB_CNF: u64 = 0x04;
pub(crate) const CAPABILITY_OFFSET: usize = 0x00;
const GENERAL_CONFIG_OFFSET: usize = 0x10;
const GENERAL_INTERRUPT_OFFSET: usize = 0x20;
pub(crate) const MAIN_COUNTER_OFFSET: usize = 0xF0;
// const NUM_TIMER_CAP_MASK: u64 = 0x0f00;
const LEG_RT_CAP: u64 = 0x8000;
const T0_CONFIG_CAPABILITY_OFFSET: usize = 0x100;
pub(crate) const T0_COMPARATOR_OFFSET: usize = 0x108;
const PER_INT_CAP: u64 = 0x10;
pub unsafe fn init(hpet: &mut Hpet) -> bool {
unsafe {
debug!("HPET @ {:#x}", { hpet.base_address.address });
debug_caps(hpet);
trace!("HPET Before Init");
debug_config(hpet);
// Disable HPET
{
let mut config_word = hpet.read_u64(GENERAL_CONFIG_OFFSET);
config_word &= !(LEG_RT_CNF | ENABLE_CNF);
hpet.write_u64(GENERAL_CONFIG_OFFSET, config_word);
}
let capability = hpet.read_u64(CAPABILITY_OFFSET);
if capability & LEG_RT_CAP == 0 {
warn!("HPET missing capability LEG_RT_CAP");
return false;
}
let period_fs = capability >> 32;
let divisor = (pit::RATE as u64 * 1_000_000) / period_fs;
let t0_capabilities = hpet.read_u64(T0_CONFIG_CAPABILITY_OFFSET);
if t0_capabilities & PER_INT_CAP == 0 {
warn!("HPET T0 missing capability PER_INT_CAP");
return false;
}
let counter = hpet.read_u64(MAIN_COUNTER_OFFSET);
let t0_config_word: u64 = TN_VAL_SET_CNF | TN_TYPE_CNF | TN_INT_ENB_CNF;
hpet.write_u64(T0_CONFIG_CAPABILITY_OFFSET, t0_config_word);
// set accumulator value
hpet.write_u64(T0_COMPARATOR_OFFSET, counter + divisor);
// set interval
hpet.write_u64(T0_COMPARATOR_OFFSET, divisor);
// Enable interrupts from the HPET
{
let mut config_word: u64 = hpet.read_u64(GENERAL_CONFIG_OFFSET);
config_word |= LEG_RT_CNF | ENABLE_CNF;
hpet.write_u64(GENERAL_CONFIG_OFFSET, config_word);
}
trace!("HPET After Init");
debug_config(hpet);
true
}
}
unsafe fn debug_caps(hpet: &mut Hpet) {
unsafe {
let capability = hpet.read_u64(CAPABILITY_OFFSET);
trace!(" caps: {:#x}", capability);
trace!(
" clock period: {:?}",
Duration::from_nanos((capability >> 32) / 1_000_000)
);
trace!(
" ID: {:#x} revision: {}",
(capability >> 16) as u16,
capability as u8
);
trace!(
" LEG_RT_CAP: {} COUNT_SIZE_CAP: {}",
capability & (1 << 15) == (1 << 15),
capability & (1 << 13) == (1 << 13)
);
// The NUM_TIM_CAP field contains the index of the last timer.
// Add 1 to get the amount of timers.
trace!(" timers: {}", ((capability >> 8) as u8 & 0x1F) + 1);
let t0_capabilities = hpet.read_u64(T0_CONFIG_CAPABILITY_OFFSET);
trace!(
" T0 interrupt routing: {:#x}",
(t0_capabilities >> 32) as u32
);
}
}
unsafe fn debug_config(hpet: &mut Hpet) {
unsafe {
let config_word = hpet.read_u64(GENERAL_CONFIG_OFFSET);
trace!(" config: {:#x}", config_word);
let interrupt_status = hpet.read_u64(GENERAL_INTERRUPT_OFFSET);
trace!(" interrupt status: {:#x}", interrupt_status);
let counter = hpet.read_u64(MAIN_COUNTER_OFFSET);
trace!(" counter: {:#x}", counter);
let t0_capabilities = hpet.read_u64(T0_CONFIG_CAPABILITY_OFFSET);
trace!(" T0 flags: {:#x}", t0_capabilities as u32);
let t0_comparator = hpet.read_u64(T0_COMPARATOR_OFFSET);
trace!(" T0 comparator: {:#x}", t0_comparator);
}
}
+427
View File
@@ -0,0 +1,427 @@
use core::{cell::SyncUnsafeCell, fmt, ptr};
use alloc::vec::Vec;
use spin::Mutex;
use super::{local_apic::ApicId, pic};
use crate::{
acpi::madt::{self, Madt, MadtEntry, MadtIntSrcOverride, MadtIoApic},
arch::{cpuid::cpuid, interrupt::irq},
memory::{map_device_memory, PhysicalAddress},
};
pub struct IoApicRegs {
pointer: *const u32,
}
impl IoApicRegs {
fn ioregsel(&self) -> *const u32 {
self.pointer
}
fn iowin(&self) -> *const u32 {
// offset 0x10
unsafe { self.pointer.offset(4) }
}
fn write_ioregsel(&mut self, value: u32) {
unsafe { ptr::write_volatile::<u32>(self.ioregsel().cast_mut(), value) }
}
fn read_iowin(&self) -> u32 {
unsafe { ptr::read_volatile::<u32>(self.iowin()) }
}
fn write_iowin(&mut self, value: u32) {
unsafe { ptr::write_volatile::<u32>(self.iowin().cast_mut(), value) }
}
fn read_reg(&mut self, reg: u8) -> u32 {
self.write_ioregsel(reg.into());
self.read_iowin()
}
fn write_reg(&mut self, reg: u8, value: u32) {
self.write_ioregsel(reg.into());
self.write_iowin(value);
}
pub fn read_ioapicid(&mut self) -> u32 {
self.read_reg(0x00)
}
pub fn read_ioapicver(&mut self) -> u32 {
self.read_reg(0x01)
}
pub fn read_ioredtbl(&mut self, idx: u8) -> u64 {
assert!(idx < 24);
let lo = self.read_reg(0x10 + idx * 2);
let hi = self.read_reg(0x10 + idx * 2 + 1);
u64::from(lo) | (u64::from(hi) << 32)
}
pub fn write_ioredtbl(&mut self, idx: u8, value: u64) {
assert!(idx < 24);
let lo = value as u32;
let hi = (value >> 32) as u32;
self.write_reg(0x10 + idx * 2, lo);
self.write_reg(0x10 + idx * 2 + 1, hi);
}
pub fn max_redirection_table_entries(&mut self) -> u8 {
let ver = self.read_ioapicver();
((ver & 0x00FF_0000) >> 16) as u8
}
#[allow(dead_code)]
pub fn id(&mut self) -> u8 {
let id_reg = self.read_ioapicid();
((id_reg & 0x0F00_0000) >> 24) as u8
}
}
pub struct IoApic {
regs: Mutex<IoApicRegs>,
gsi_start: u32,
count: u8,
}
unsafe impl Send for IoApic {}
unsafe impl Sync for IoApic {}
impl IoApic {
#[allow(dead_code)]
pub fn new(regs_base: *const u32, gsi_start: u32) -> Self {
let mut regs = IoApicRegs { pointer: regs_base };
let count = regs.max_redirection_table_entries();
Self {
regs: Mutex::new(regs),
gsi_start,
count,
}
}
/// Map an interrupt vector to a physical local APIC ID of a processor (thus physical mode).
#[allow(dead_code)]
pub fn map(&self, idx: u8, info: MapInfo) {
self.regs.lock().write_ioredtbl(idx, info.as_raw())
}
pub fn set_mask(&self, gsi: u32, mask: bool) {
let idx = (gsi - self.gsi_start) as u8;
let mut guard = self.regs.lock();
let mut reg = guard.read_ioredtbl(idx);
reg &= !(1 << 16);
reg |= u64::from(mask) << 16;
guard.write_ioredtbl(idx, reg);
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ApicTriggerMode {
Edge = 0,
Level = 1,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ApicPolarity {
ActiveHigh = 0,
ActiveLow = 1,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
#[allow(unused)]
pub enum DestinationMode {
Physical = 0,
Logical = 1,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
#[allow(unused)]
pub enum DeliveryMode {
Fixed = 0b000,
LowestPriority = 0b001,
Smi = 0b010,
Nmi = 0b100,
Init = 0b101,
ExtInt = 0b111,
}
#[derive(Clone, Copy, Debug)]
pub struct MapInfo {
pub dest: ApicId,
pub mask: bool,
pub trigger_mode: ApicTriggerMode,
pub polarity: ApicPolarity,
pub dest_mode: DestinationMode,
pub delivery_mode: DeliveryMode,
pub vector: u8,
}
impl MapInfo {
pub fn as_raw(&self) -> u64 {
assert!(self.vector >= 0x20);
assert!(self.vector <= 0xFE);
// TODO: Check for reserved fields.
(u64::from(self.dest.get()) << 56)
| (u64::from(self.mask) << 16)
| ((self.trigger_mode as u64) << 15)
| ((self.polarity as u64) << 13)
| ((self.dest_mode as u64) << 11)
| ((self.delivery_mode as u64) << 8)
| u64::from(self.vector)
}
}
impl fmt::Debug for IoApic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
struct RedirTable<'a>(&'a Mutex<IoApicRegs>);
impl fmt::Debug for RedirTable<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut guard = self.0.lock();
let count = guard.max_redirection_table_entries();
f.debug_list()
.entries((0..count).map(|i| guard.read_ioredtbl(i)))
.finish()
}
}
f.debug_struct("IoApic")
.field("redir_table", &RedirTable(&self.regs))
.field("gsi_start", &self.gsi_start)
.field("count", &self.count)
.finish()
}
}
#[derive(Clone, Copy, Debug)]
pub enum TriggerMode {
ConformsToSpecs,
Edge,
Level,
}
#[derive(Clone, Copy, Debug)]
pub enum Polarity {
ConformsToSpecs,
ActiveHigh,
ActiveLow,
}
#[derive(Clone, Copy, Debug)]
pub struct Override {
bus_irq: u8,
gsi: u32,
trigger_mode: TriggerMode,
polarity: Polarity,
}
// static mut because only the AP initializes the I/O Apic, and when that is done, it's solely
// accessed immutably.
static IOAPICS: SyncUnsafeCell<Option<Vec<IoApic>>> = SyncUnsafeCell::new(None);
// static mut for the same reason as above
static SRC_OVERRIDES: SyncUnsafeCell<Option<Vec<Override>>> = SyncUnsafeCell::new(None);
pub fn ioapics() -> &'static [IoApic] {
unsafe { &*IOAPICS.get() }
.as_ref()
.map_or(&[], |vector| &vector[..])
}
pub fn src_overrides() -> &'static [Override] {
unsafe { &*SRC_OVERRIDES.get() }
.as_ref()
.map_or(&[], |vector| &vector[..])
}
pub unsafe fn handle_ioapic(madt_ioapic: &'static MadtIoApic) {
unsafe {
// map the I/O APIC registers
let virt = map_device_memory(PhysicalAddress::new(madt_ioapic.address as usize), 4096);
let ioapic_registers = virt.data() as *const u32;
let ioapic = IoApic::new(ioapic_registers, madt_ioapic.gsi_base);
assert_eq!(
ioapic.regs.lock().id(),
madt_ioapic.id,
"mismatched ACPI MADT I/O APIC ID, and the ID reported by the I/O APIC"
);
(*IOAPICS.get()).get_or_insert_with(Vec::new).push(ioapic);
}
}
pub unsafe fn handle_src_override(src_override: &'static MadtIntSrcOverride) {
unsafe {
let flags = src_override.flags;
let polarity_raw = (flags & 0x0003) as u8;
let trigger_mode_raw = ((flags & 0x000C) >> 2) as u8;
let polarity = match polarity_raw {
0b00 => Polarity::ConformsToSpecs,
0b01 => Polarity::ActiveHigh,
0b10 => return, // reserved
0b11 => Polarity::ActiveLow,
_ => unreachable!(),
};
let trigger_mode = match trigger_mode_raw {
0b00 => TriggerMode::ConformsToSpecs,
0b01 => TriggerMode::Edge,
0b10 => return, // reserved
0b11 => TriggerMode::Level,
_ => unreachable!(),
};
let over = Override {
bus_irq: src_override.irq_source,
gsi: src_override.gsi_base,
polarity,
trigger_mode,
};
(*SRC_OVERRIDES.get())
.get_or_insert_with(Vec::new)
.push(over);
}
}
#[allow(dead_code)]
pub unsafe fn init() {
unsafe {
let bsp_apic_id = ApicId::new(u32::from(
cpuid().get_feature_info().unwrap().initial_local_apic_id(),
)); // TODO: remove unwraps
// search the madt for all IOAPICs.
if cfg!(feature = "acpi") {
let madt: &'static Madt = match madt::madt() {
Some(m) => m,
// TODO: Parse MP tables too.
None => return,
};
if madt.flags & madt::FLAG_PCAT != 0 {
pic::disable();
}
// find all I/O APICs (usually one).
for entry in madt.iter() {
match entry {
MadtEntry::IoApic(ioapic) => handle_ioapic(ioapic),
MadtEntry::IntSrcOverride(src_override) => handle_src_override(src_override),
_ => (),
}
}
}
println!(
"I/O APICs: {:?}, overrides: {:?}",
ioapics(),
src_overrides()
);
// map the legacy PC-compatible IRQs (0-15) to 32-47, just like we did with 8259 PIC (if it
// wouldn't have been disabled due to this I/O APIC)
for legacy_irq in 0..=15 {
let (gsi, trigger_mode, polarity) = match get_override(legacy_irq) {
Some(over) => (over.gsi, over.trigger_mode, over.polarity),
None => {
if src_overrides()
.iter()
.any(|over| over.gsi == u32::from(legacy_irq) && over.bus_irq != legacy_irq)
&& !src_overrides()
.iter()
.any(|over| over.bus_irq == legacy_irq)
{
// there's an IRQ conflict, making this legacy IRQ inaccessible.
continue;
}
(
legacy_irq.into(),
TriggerMode::ConformsToSpecs,
Polarity::ConformsToSpecs,
)
}
};
let apic = match find_ioapic(gsi) {
Some(ioapic) => ioapic,
None => {
println!("Unable to find a suitable APIC for legacy IRQ {} (GSI {}). It will not be mapped.", legacy_irq, gsi);
continue;
}
};
let redir_tbl_index = (gsi - apic.gsi_start) as u8;
let map_info = MapInfo {
// only send to the BSP
dest: bsp_apic_id,
dest_mode: DestinationMode::Physical,
delivery_mode: DeliveryMode::Fixed,
mask: false,
polarity: match polarity {
Polarity::ActiveHigh => ApicPolarity::ActiveHigh,
Polarity::ActiveLow => ApicPolarity::ActiveLow,
Polarity::ConformsToSpecs => ApicPolarity::ActiveHigh,
},
trigger_mode: match trigger_mode {
TriggerMode::Edge => ApicTriggerMode::Edge,
TriggerMode::Level => ApicTriggerMode::Level,
TriggerMode::ConformsToSpecs => ApicTriggerMode::Edge,
},
vector: 32 + legacy_irq,
};
apic.map(redir_tbl_index, map_info);
}
println!(
"I/O APICs: {:?}, overrides: {:?}",
ioapics(),
src_overrides()
);
irq::set_irq_method(irq::IrqMethod::Apic);
// tell the firmware that we're using APIC rather than the default 8259 PIC.
// FIXME: With ACPI moved to userspace, we should instead allow userspace to check whether the
// IOAPIC has been initialized, and then subsequently let some ACPI driver call the AML from
// userspace.
/*#[cfg(feature = "acpi")]
{
let method = {
let namespace_guard = crate::acpi::ACPI_TABLE.namespace.read();
if let Some(value) = namespace_guard.as_ref().unwrap().get("\\_PIC") {
value.get_as_method().ok()
} else {
None
}
};
if let Some(m) = method {
m.execute("\\_PIC".into(), vec!(crate::acpi::aml::AmlValue::Integer(1)));
}
}*/
}
}
fn get_override(irq: u8) -> Option<&'static Override> {
src_overrides().iter().find(|over| over.bus_irq == irq)
}
fn resolve(irq: u8) -> u32 {
get_override(irq).map_or(u32::from(irq), |over| over.gsi)
}
fn find_ioapic(gsi: u32) -> Option<&'static IoApic> {
ioapics()
.iter()
.find(|apic| gsi >= apic.gsi_start && gsi < apic.gsi_start + u32::from(apic.count))
}
pub unsafe fn mask(irq: u8) {
let gsi = resolve(irq);
let apic = match find_ioapic(gsi) {
Some(a) => a,
None => return,
};
apic.set_mask(gsi, true);
}
pub unsafe fn unmask(irq: u8) {
let gsi = resolve(irq);
let apic = match find_ioapic(gsi) {
Some(a) => a,
None => return,
};
apic.set_mask(gsi, false);
}
+272
View File
@@ -0,0 +1,272 @@
use core::{
cell::SyncUnsafeCell,
ptr::{read_volatile, write_volatile},
};
use x86::msr::*;
use crate::{
arch::{cpuid::cpuid, ipi::IpiKind},
memory::{map_device_memory, PhysicalAddress},
percpu::PercpuBlock,
};
#[derive(Clone, Copy, Debug)]
pub struct ApicId(u32);
impl ApicId {
pub fn new(inner: u32) -> Self {
Self(inner)
}
pub fn get(&self) -> u32 {
self.0
}
}
static LOCAL_APIC: SyncUnsafeCell<LocalApic> = SyncUnsafeCell::new(LocalApic {
address: 0,
x2: false,
});
pub unsafe fn the_local_apic() -> &'static mut LocalApic {
unsafe { &mut *LOCAL_APIC.get() }
}
pub unsafe fn init() {
unsafe {
the_local_apic().init();
}
}
pub unsafe fn init_ap() {
unsafe {
the_local_apic().init_ap();
}
}
/// Local APIC
pub struct LocalApic {
pub address: usize,
pub x2: bool,
}
impl LocalApic {
unsafe fn init(&mut self) {
unsafe {
let physaddr = PhysicalAddress::new(rdmsr(IA32_APIC_BASE) as usize & 0xFFFF_0000);
self.x2 = cpuid()
.get_feature_info()
.is_some_and(|feature_info| feature_info.has_x2apic());
if !self.x2 {
debug!("Detected xAPIC at {:#x}", physaddr.data());
self.address = map_device_memory(physaddr, 4096).data();
} else {
debug!("Detected x2APIC");
}
self.init_ap();
}
}
unsafe fn init_ap(&mut self) {
unsafe {
if self.x2 {
wrmsr(IA32_APIC_BASE, rdmsr(IA32_APIC_BASE) | (1 << 10));
wrmsr(IA32_X2APIC_SIVR, 0x100);
} else {
self.write(0xF0, 0x100);
}
self.setup_error_int();
//self.setup_timer();
PercpuBlock::current()
.misc_arch_info
.apic_id_opt
.set(Some(self.id()));
}
}
unsafe fn read(&self, reg: u32) -> u32 {
debug_assert!(!self.x2);
unsafe { read_volatile((self.address + reg as usize) as *const u32) }
}
unsafe fn write(&mut self, reg: u32, value: u32) {
debug_assert!(!self.x2);
unsafe {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
}
pub fn id(&self) -> ApicId {
ApicId::new(if self.x2 {
unsafe { rdmsr(IA32_X2APIC_APICID) as u32 }
} else {
unsafe { self.read(0x20) }
})
}
pub fn version(&self) -> u32 {
if self.x2 {
unsafe { rdmsr(IA32_X2APIC_VERSION) as u32 }
} else {
unsafe { self.read(0x30) }
}
}
pub fn icr(&self) -> u64 {
if self.x2 {
unsafe { rdmsr(IA32_X2APIC_ICR) }
} else {
unsafe { ((self.read(0x310) as u64) << 32) | self.read(0x300) as u64 }
}
}
pub fn set_icr(&mut self, value: u64) {
if self.x2 {
unsafe {
wrmsr(IA32_X2APIC_ICR, value);
}
} else {
unsafe {
const PENDING: u32 = 1 << 12;
while self.read(0x300) & PENDING == PENDING {
core::hint::spin_loop();
}
self.write(0x310, (value >> 32) as u32);
self.write(0x300, value as u32);
while self.read(0x300) & PENDING == PENDING {
core::hint::spin_loop();
}
}
}
}
pub fn ipi(&mut self, apic_id: ApicId, kind: IpiKind) {
let shift = if self.x2 { 32 } else { 56 };
self.set_icr((u64::from(apic_id.get()) << shift) | 0x40 | kind as u64);
}
pub fn ipi_nmi(&mut self, apic_id: ApicId) {
let shift = if self.x2 { 32 } else { 56 };
self.set_icr((u64::from(apic_id.get()) << shift) | (1 << 14) | (0b100 << 8));
}
pub unsafe fn eoi(&mut self) {
unsafe {
if self.x2 {
wrmsr(IA32_X2APIC_EOI, 0);
} else {
self.write(0xB0, 0);
}
}
}
/// Reads the Error Status Register.
pub unsafe fn esr(&mut self) -> u32 {
unsafe {
if self.x2 {
// update the ESR to the current state of the local apic.
wrmsr(IA32_X2APIC_ESR, 0);
// read the updated value
rdmsr(IA32_X2APIC_ESR) as u32
} else {
self.write(0x280, 0);
self.read(0x280)
}
}
}
pub unsafe fn lvt_timer(&mut self) -> u32 {
unsafe {
if self.x2 {
rdmsr(IA32_X2APIC_LVT_TIMER) as u32
} else {
self.read(0x320)
}
}
}
pub unsafe fn set_lvt_timer(&mut self, value: u32) {
unsafe {
if self.x2 {
wrmsr(IA32_X2APIC_LVT_TIMER, u64::from(value));
} else {
self.write(0x320, value);
}
}
}
pub unsafe fn init_count(&mut self) -> u32 {
unsafe {
if self.x2 {
rdmsr(IA32_X2APIC_INIT_COUNT) as u32
} else {
self.read(0x380)
}
}
}
pub unsafe fn set_init_count(&mut self, initial_count: u32) {
unsafe {
if self.x2 {
wrmsr(IA32_X2APIC_INIT_COUNT, u64::from(initial_count));
} else {
self.write(0x380, initial_count);
}
}
}
pub unsafe fn cur_count(&mut self) -> u32 {
unsafe {
if self.x2 {
rdmsr(IA32_X2APIC_CUR_COUNT) as u32
} else {
self.read(0x390)
}
}
}
pub unsafe fn div_conf(&mut self) -> u32 {
unsafe {
if self.x2 {
rdmsr(IA32_X2APIC_DIV_CONF) as u32
} else {
self.read(0x3E0)
}
}
}
pub unsafe fn set_div_conf(&mut self, div_conf: u32) {
unsafe {
if self.x2 {
wrmsr(IA32_X2APIC_DIV_CONF, u64::from(div_conf));
} else {
self.write(0x3E0, div_conf);
}
}
}
pub unsafe fn lvt_error(&mut self) -> u32 {
unsafe {
if self.x2 {
rdmsr(IA32_X2APIC_LVT_ERROR) as u32
} else {
self.read(0x370)
}
}
}
pub unsafe fn set_lvt_error(&mut self, lvt_error: u32) {
unsafe {
if self.x2 {
wrmsr(IA32_X2APIC_LVT_ERROR, u64::from(lvt_error));
} else {
self.write(0x370, lvt_error);
}
}
}
unsafe fn setup_error_int(&mut self) {
unsafe {
let vector = 49u32;
self.set_lvt_error(vector);
}
}
}
#[repr(u8)]
pub enum LvtTimerMode {
OneShot = 0b00,
Periodic = 0b01,
TscDeadline = 0b10,
}
+94
View File
@@ -0,0 +1,94 @@
use core::cell::Cell;
pub mod cpu;
pub mod hpet;
pub mod ioapic;
pub mod local_apic;
pub mod pic;
pub mod pit;
pub mod serial;
#[cfg(feature = "system76_ec_debug")]
pub mod system76_ec;
#[cfg(feature = "x86_kvm_pv")]
pub mod tsc;
pub unsafe fn init() {
unsafe {
pic::init();
local_apic::init();
// Run here for the side effect of printing if KVM was used to avoid interleaved logs.
tsc::get_kvm_support();
}
}
pub unsafe fn init_after_acpi() {
// this will disable the IOAPIC if needed.
//ioapic::init(mapper);
}
unsafe fn init_hpet() -> bool {
if cfg!(not(feature = "acpi")) {
return false;
}
unsafe {
use crate::acpi::ACPI_TABLE;
match *ACPI_TABLE.hpet.write() {
Some(ref mut hpet) => {
if cfg!(target_arch = "x86") {
//TODO: fix HPET on i686
warn!("HPET found but implemented on i686");
return false;
}
hpet::init(hpet)
}
_ => false,
}
}
}
pub unsafe fn init_noncore() {
unsafe {
debug!("Initializing system timer");
#[cfg(feature = "x86_kvm_pv")]
if tsc::init() {
debug!("TSC used as system clock source");
}
if init_hpet() {
debug!("HPET used as system timer");
} else {
pit::init();
debug!("PIT used as system timer");
}
debug!("Finished initializing devices");
}
}
pub unsafe fn init_ap() {
unsafe {
local_apic::init_ap();
#[cfg(feature = "x86_kvm_pv")]
tsc::init();
}
}
pub struct ArchPercpuMisc {
pub apic_id_opt: Cell<Option<local_apic::ApicId>>,
#[cfg(feature = "x86_kvm_pv")]
pub tsc_info: tsc::TscPercpu,
}
impl ArchPercpuMisc {
pub const fn default() -> Self {
Self {
apic_id_opt: Cell::new(None),
#[cfg(feature = "x86_kvm_pv")]
tsc_info: tsc::TscPercpu::default(),
}
}
}
+98
View File
@@ -0,0 +1,98 @@
use core::cell::SyncUnsafeCell;
use crate::{
arch::interrupt::irq,
syscall::io::{Io, Pio},
};
static MASTER: SyncUnsafeCell<Pic> = SyncUnsafeCell::new(Pic::new(0x20));
static SLAVE: SyncUnsafeCell<Pic> = SyncUnsafeCell::new(Pic::new(0xA0));
// SAFETY: must be main thread
pub unsafe fn master<'a>() -> &'a mut Pic {
unsafe { &mut *MASTER.get() }
}
// SAFETY: must be main thread
pub unsafe fn slave<'a>() -> &'a mut Pic {
unsafe { &mut *SLAVE.get() }
}
pub unsafe fn init() {
unsafe {
let master = master();
let slave = slave();
// Start initialization
master.cmd.write(0x11);
slave.cmd.write(0x11);
// Set offsets
master.data.write(0x20);
slave.data.write(0x28);
// Set up cascade
master.data.write(4);
slave.data.write(2);
// Set up interrupt mode (1 is 8086/88 mode, 2 is auto EOI)
master.data.write(1);
slave.data.write(1);
// Unmask interrupts
master.data.write(0);
slave.data.write(0);
// Ack remaining interrupts
master.ack();
slave.ack();
// probably already set to PIC, but double-check
irq::set_irq_method(irq::IrqMethod::Pic);
}
}
pub unsafe fn disable() {
unsafe {
master().data.write(0xFF);
slave().data.write(0xFF);
}
}
pub struct Pic {
cmd: Pio<u8>,
data: Pio<u8>,
}
impl Pic {
pub const fn new(port: u16) -> Pic {
Pic {
cmd: Pio::new(port),
data: Pio::new(port + 1),
}
}
pub fn ack(&mut self) {
self.cmd.write(0x20);
}
pub fn mask_set(&mut self, irq: u8) {
assert!(irq < 8);
let mut mask = self.data.read();
mask |= 1 << irq;
self.data.write(mask);
}
pub fn mask_clear(&mut self, irq: u8) {
assert!(irq < 8);
let mut mask = self.data.read();
mask &= !(1 << irq);
self.data.write(mask);
}
/// A bitmap of all currently servicing IRQs. Spurious IRQs will not have this bit set
pub fn isr(&mut self) -> u8 {
self.cmd.write(0x0A);
self.cmd.read() // note that cmd is read, rather than data
}
}
+50
View File
@@ -0,0 +1,50 @@
use core::cell::SyncUnsafeCell;
use crate::syscall::io::{Io, Pio};
static CHAN0: SyncUnsafeCell<Pio<u8>> = SyncUnsafeCell::new(Pio::new(0x40));
//pub static mut CHAN1: Pio<u8> = Pio::new(0x41);
//pub static mut CHAN2: Pio<u8> = Pio::new(0x42);
static COMMAND: SyncUnsafeCell<Pio<u8>> = SyncUnsafeCell::new(Pio::new(0x43));
// SAFETY: must be externally syncd
pub unsafe fn chan0<'a>() -> &'a mut Pio<u8> {
unsafe { &mut *CHAN0.get() }
}
// SAFETY: must be externally syncd
pub unsafe fn command<'a>() -> &'a mut Pio<u8> {
unsafe { &mut *COMMAND.get() }
}
const SELECT_CHAN0: u8 = 0b00 << 6;
const ACCESS_LATCH: u8 = 0b00 << 4;
const ACCESS_LOHI: u8 = 0b11 << 4;
const MODE_2: u8 = 0b010 << 1;
// 1 / (1.193182 MHz) = 838,095,110 femtoseconds ~= 838.095 ns
pub const PERIOD_FS: u128 = 838_095_110;
// 4847 / (1.193182 MHz) = 4,062,247 ns ~= 4.1 ms or 246 Hz
pub const CHAN0_DIVISOR: u16 = 4847;
// Calculated interrupt period in nanoseconds based on divisor and period
pub const RATE: u128 = (CHAN0_DIVISOR as u128 * PERIOD_FS) / 1_000_000;
pub unsafe fn init() {
unsafe {
command().write(SELECT_CHAN0 | ACCESS_LOHI | MODE_2);
chan0().write(CHAN0_DIVISOR as u8);
chan0().write((CHAN0_DIVISOR >> 8) as u8);
}
}
pub unsafe fn read() -> u16 {
unsafe {
command().write(SELECT_CHAN0 | ACCESS_LATCH);
let low = chan0().read();
let high = chan0().read();
let counter = ((high as u16) << 8) | (low as u16);
// Counter is inverted, subtract from CHAN0_DIVISOR
CHAN0_DIVISOR.saturating_sub(counter)
}
}
View File
+48
View File
@@ -0,0 +1,48 @@
use crate::{
devices::{serial::SerialKind, uart_16550::SerialPort},
memory::map_device_memory,
syscall::io::{Mmio, Pio},
};
use spin::Mutex;
pub static COM1: Mutex<SerialKind> = Mutex::new(SerialKind::NotPresent);
pub static COM2: Mutex<SerialKind> = Mutex::new(SerialKind::NotPresent);
pub static LPSS: Mutex<SerialKind> = Mutex::new(SerialKind::NotPresent);
pub unsafe fn init() {
#[cfg(feature = "system76_ec_debug")]
super::system76_ec::init();
if cfg!(not(feature = "serial_debug")) {
// FIXME remove serial_debug feature once ACPI SPCR is respected on UEFI boots.
return;
}
let mut com1 = SerialPort::<Pio<u8>>::new(0x3F8);
if com1.init().is_ok() {
*COM1.lock() = SerialKind::Ns16550Pio(com1);
}
let mut com2 = SerialPort::<Pio<u8>>::new(0x2F8);
if com2.init().is_ok() {
*COM2.lock() = SerialKind::Ns16550Pio(com2);
}
// FIXME remove explicit LPSS handling once ACPI SPCR is supported
if cfg!(not(feature = "lpss_debug")) {
return;
}
let virt = unsafe {
map_device_memory(
// TODO: Make this configurable
crate::memory::PhysicalAddress::new(0xFE032000),
4,
)
};
let lpss = unsafe { SerialPort::<Mmio<u32>>::new(virt.data()) };
if lpss.init().is_ok() {
*LPSS.lock() = SerialKind::Ns16550u32(lpss);
}
}
+89
View File
@@ -0,0 +1,89 @@
use spin::Mutex;
use syscall::io::{Io, Pio};
pub static SYSTEM76_EC: Mutex<Option<System76Ec>> = Mutex::new(None);
pub fn init() {
*SYSTEM76_EC.lock() = System76Ec::new();
}
pub struct System76Ec {
base: u16,
}
impl System76Ec {
pub fn new() -> Option<Self> {
let mut system76_ec = Self { base: 0x0E00 };
if system76_ec.probe() {
Some(system76_ec)
} else {
None
}
}
#[inline(always)]
pub fn read(&mut self, addr: u8) -> u8 {
Pio::<u8>::new(self.base + addr as u16).read()
}
#[inline(always)]
pub fn write(&mut self, addr: u8, data: u8) {
Pio::<u8>::new(self.base + addr as u16).write(data)
}
pub fn probe(&mut self) -> bool {
// Send probe command
self.write(0, 1);
// Wait for response
let mut timeout = 1_000_000;
while timeout > 0 {
if self.read(0) == 0 {
break;
}
timeout -= 1;
}
if timeout == 0 {
return false;
}
// Return false on command error
if self.read(1) != 0 {
return false;
}
// Must receive 0x76, 0xEC as signature
self.read(2) == 0x76 && self.read(3) == 0xEC
}
pub fn flush(&mut self) {
// Send command
self.write(0, 4);
// TODO: timeout
while self.read(0) != 0 {}
// Clear length
self.write(3, 0);
}
pub fn print(&mut self, byte: u8) {
// Read length
let len = self.read(3);
// Write data at offset
self.write(len + 4, byte);
// Update length
self.write(3, len + 1);
// If we hit the end of the buffer, or were given a newline, flush
if byte == b'\n' || len >= 128 {
self.flush();
}
}
pub fn print_slice(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.print(byte);
}
}
}
+161
View File
@@ -0,0 +1,161 @@
use core::{cell::Cell, ptr::addr_of};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::__cpuid;
#[cfg(target_arch = "x86")]
use core::arch::x86::__cpuid;
use rmm::Arch;
use spin::Once;
use crate::{memory::allocate_frame, percpu::PercpuBlock};
pub struct KvmSupport {
max_leaf: u32,
supp_feats: KvmFeatureBits,
}
bitflags! {
// https://www.kernel.org/doc/html/latest/virt/kvm/x86/cpuid.html
#[derive(Debug)]
struct KvmFeatureBits: u32 {
const CLOCKSOURCE = 1 << 0;
const CLOCKSOURCE2 = 1 << 3;
const CLOCKSOURCE_STABLE = 1 << 24;
}
}
// https://www.kernel.org/doc/html/v5.9/virt/kvm/msr.html
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
struct PvclockVcpuTimeInfo {
version: u32,
pad: u32,
tsc_timestamp: u64,
system_time: u64,
tsc_to_system_mul: u32,
tsc_shift: i8,
flags: u8,
_pad: [u8; 2],
}
const MSR_KVM_SYSTEM_TIME_NEW: u32 = 0x4b564d01;
const MSR_KVM_WALL_CLOCK_NEW: u32 = 0x4b564d00;
pub struct TscPercpu {
vcpu_page: Cell<*const PvclockVcpuTimeInfo>,
prev: Cell<u128>,
}
impl TscPercpu {
pub const fn default() -> Self {
Self {
vcpu_page: Cell::new(core::ptr::null()),
prev: Cell::new(0),
}
}
}
pub fn monotonic_absolute() -> Option<u128> {
let inf = &PercpuBlock::current().misc_arch_info.tsc_info;
let ptr = inf.vcpu_page.get();
if ptr.is_null() {
return None;
}
loop {
unsafe {
let cur_version = addr_of!((*ptr).version).read_volatile();
if cur_version & 1 == 1 {
continue;
}
let elapsed_ticks =
x86::time::rdtsc().saturating_sub(addr_of!((*ptr).tsc_timestamp).read_volatile());
let tsc_shift = addr_of!((*ptr).tsc_shift).read_volatile();
let elapsed = if tsc_shift >= 0 {
elapsed_ticks.checked_shl(tsc_shift as u32).unwrap()
} else {
elapsed_ticks.checked_shr((-tsc_shift) as u32).unwrap()
};
let system_time = addr_of!((*ptr).system_time).read_volatile();
let tsc_to_system_mul = addr_of!((*ptr).tsc_to_system_mul).read_volatile();
let new_version = addr_of!((*ptr).version).read_volatile();
if new_version != cur_version || new_version & 1 == 1 {
continue;
}
let delta = (u128::from(elapsed) * u128::from(tsc_to_system_mul)) >> 32;
let time = u128::from(system_time) + delta;
let prev = inf.prev.replace(time);
if prev > time {
// TODO
error!("TSC wraparound ({prev} > {time})");
return None;
}
assert!(prev <= time);
return Some(time);
}
}
}
pub fn get_kvm_support() -> &'static Option<KvmSupport> {
static KVM_SUPPORT: Once<Option<KvmSupport>> = Once::new();
KVM_SUPPORT.call_once(|| {
let res = unsafe { __cpuid(0x4000_0000) };
if [res.ebx, res.ecx, res.edx].map(u32::to_le_bytes) != [*b"KVMK", *b"VMKV", *b"M\0\0\0"] {
return None;
}
let max_leaf = res.eax;
if max_leaf < 0x4000_0001 {
return None;
}
let res = unsafe { __cpuid(0x4000_0001) };
let supp_feats = KvmFeatureBits::from_bits_retain(res.eax);
debug!("Detected KVM paravirtualization support, features {supp_feats:?}");
Some(KvmSupport {
max_leaf,
supp_feats,
})
})
}
pub unsafe fn init() -> bool {
unsafe {
let cpuid = crate::arch::cpuid::cpuid();
if !cpuid.get_feature_info().is_some_and(|f| f.has_tsc()) {
return false;
}
let kvm_support = get_kvm_support();
if let Some(kvm_support) = kvm_support
&& kvm_support
.supp_feats
.contains(KvmFeatureBits::CLOCKSOURCE2 | KvmFeatureBits::CLOCKSOURCE_STABLE)
{
let frame = allocate_frame().expect("failed to allocate timer page");
x86::msr::wrmsr(MSR_KVM_SYSTEM_TIME_NEW, (frame.base().data() as u64) | 1);
let ptr = crate::memory::RmmA::phys_to_virt(frame.base()).data()
as *const PvclockVcpuTimeInfo;
PercpuBlock::current()
.misc_arch_info
.tsc_info
.vcpu_page
.set(ptr);
/*let tsc_ghz = loop {
let val1 = ptr.read_volatile();
let val2 = ptr.read_volatile();
if val1.version & 1 == 1 || val2.version & 1 == 1 || val1.version != val2.version {
continue;
}
let val1
break tsc_hz / 1_000_000_000;
};*/
true
} else {
false
}
}
}
+431
View File
@@ -0,0 +1,431 @@
//! Global descriptor table
use core::ptr;
#[cfg(target_arch = "x86")]
use x86::bits32::task::TaskStateSegment;
#[cfg(target_arch = "x86_64")]
use x86::bits64::task::TaskStateSegment;
use x86::{
dtables::{self, DescriptorTablePointer},
segmentation::{self, Descriptor as SegmentDescriptor, SegmentSelector},
task, Ring,
};
use crate::{
cpu_set::LogicalCpuId,
memory::{RmmA, RmmArch, PAGE_SIZE},
percpu::PercpuBlock,
};
pub const GDT_NULL: usize = 0;
pub const GDT_KERNEL_CODE: usize = 1;
pub const GDT_KERNEL_DATA: usize = 2;
#[cfg(target_arch = "x86")]
pub const GDT_KERNEL_PERCPU: usize = 3;
#[cfg(target_arch = "x86_64")]
pub const GDT_USER_CODE32_UNUSED: usize = 3;
pub const GDT_USER_DATA: usize = 4;
pub const GDT_USER_CODE: usize = 5;
#[cfg(target_arch = "x86")]
pub const GDT_USER_FS: usize = 6;
#[cfg(target_arch = "x86")]
pub const GDT_USER_GS: usize = 7;
#[cfg(target_arch = "x86")]
pub const GDT_TSS: usize = 8;
#[cfg(target_arch = "x86_64")]
pub const GDT_TSS: usize = 6;
#[cfg(target_arch = "x86_64")]
pub const GDT_TSS_HIGH: usize = 7;
pub const GDT_A_PRESENT: u8 = 1 << 7;
pub const GDT_A_RING_0: u8 = 0 << 5;
pub const GDT_A_RING_1: u8 = 1 << 5;
pub const GDT_A_RING_2: u8 = 2 << 5;
pub const GDT_A_RING_3: u8 = 3 << 5;
pub const GDT_A_SYSTEM: u8 = 1 << 4;
pub const GDT_A_EXECUTABLE: u8 = 1 << 3;
pub const GDT_A_CONFORMING: u8 = 1 << 2;
pub const GDT_A_PRIVILEGE: u8 = 1 << 1;
pub const GDT_A_DIRTY: u8 = 1;
pub const GDT_A_TSS_AVAIL: u8 = 0x9;
pub const GDT_A_TSS_BUSY: u8 = 0xB;
pub const GDT_F_PAGE_SIZE: u8 = 1 << 7;
pub const GDT_F_PROTECTED_MODE: u8 = 1 << 6;
pub const GDT_F_LONG_MODE: u8 = 1 << 5;
const IOBITMAP_SIZE: u32 = 65536 / 8;
#[cfg(target_arch = "x86")]
const SEGMENT_LIMIT: u32 = 0xFFFFF;
#[cfg(target_arch = "x86_64")]
const SEGMENT_LIMIT: u32 = 0;
#[cfg(target_arch = "x86")]
const SEGMENT_FLAGS: u8 = GDT_F_PAGE_SIZE | GDT_F_PROTECTED_MODE;
#[cfg(target_arch = "x86_64")]
const SEGMENT_FLAGS: u8 = GDT_F_LONG_MODE;
#[cfg(target_arch = "x86")]
const SEGMENT_COUNT: usize = 9;
#[cfg(target_arch = "x86_64")]
const SEGMENT_COUNT: usize = 8;
// Later copied into the actual GDT with various fields set.
const BASE_GDT: [GdtEntry; SEGMENT_COUNT] = [
// Null
GdtEntry::new(0, 0, 0, 0),
// Kernel code
GdtEntry::new(
0,
SEGMENT_LIMIT,
GDT_A_PRESENT | GDT_A_RING_0 | GDT_A_SYSTEM | GDT_A_EXECUTABLE | GDT_A_PRIVILEGE,
SEGMENT_FLAGS,
),
// Kernel data
GdtEntry::new(
0,
SEGMENT_LIMIT,
GDT_A_PRESENT | GDT_A_RING_0 | GDT_A_SYSTEM | GDT_A_PRIVILEGE,
SEGMENT_FLAGS,
),
// Kernel TLS
#[cfg(target_arch = "x86")]
GdtEntry::new(
0,
SEGMENT_LIMIT,
GDT_A_PRESENT | GDT_A_RING_0 | GDT_A_SYSTEM | GDT_A_PRIVILEGE,
SEGMENT_FLAGS,
),
// Dummy 32-bit user code - apparently necessary for SYSRET. We restrict it to ring 0 anyway.
#[cfg(target_arch = "x86_64")]
GdtEntry::new(
0,
0,
GDT_A_PRESENT | GDT_A_RING_0 | GDT_A_SYSTEM | GDT_A_EXECUTABLE | GDT_A_PRIVILEGE,
GDT_F_PROTECTED_MODE,
),
// User data
GdtEntry::new(
0,
SEGMENT_LIMIT,
GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_PRIVILEGE,
SEGMENT_FLAGS,
),
// User code
GdtEntry::new(
0,
SEGMENT_LIMIT,
GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_EXECUTABLE | GDT_A_PRIVILEGE,
SEGMENT_FLAGS,
),
// User FS (for TLS)
#[cfg(target_arch = "x86")]
GdtEntry::new(
0,
SEGMENT_LIMIT,
GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_PRIVILEGE,
SEGMENT_FLAGS,
),
// User GS (for TLS)
#[cfg(target_arch = "x86")]
GdtEntry::new(
0,
SEGMENT_LIMIT,
GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_PRIVILEGE,
SEGMENT_FLAGS,
),
// TSS
GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_TSS_AVAIL, 0),
// TSS must be 16 bytes long, twice the normal size
#[cfg(target_arch = "x86_64")]
GdtEntry::new(0, 0, 0, 0),
];
#[repr(C, align(16))]
struct Align([u64; 2]);
#[repr(C, align(4096))]
pub struct ProcessorControlRegion {
// TODO: When both KASLR and KPTI are implemented, the PCR may need to be split into two pages,
// such that "secret" kernel addresses are only stored in the protected half.
pub self_ref: *mut ProcessorControlRegion,
pub user_rsp_tmp: usize,
// The GDT *must* be stored in the PCR! The paranoid interrupt handler, lacking a reliable way
// to correctly obtain GSBASE, uses SGDT to calculate the PCR offset.
pub gdt: [GdtEntry; SEGMENT_COUNT],
pub percpu: PercpuBlock,
_rsvd: Align,
pub tss: TaskStateSegment,
// These two fields are read by the CPU, but not currently modified by the kernel. Instead, the
// kernel sets the `iomap_base` field in the TSS, to either point to this bitmap, or outside
// the TSS, in which case userspace is not granted port IO access.
pub _iobitmap: [u8; IOBITMAP_SIZE as usize],
pub _all_ones: u8,
}
const _: () = {
if core::mem::offset_of!(ProcessorControlRegion, tss) % 16 != 0 {
panic!("PCR is incorrectly defined, TSS alignment is too small");
}
if core::mem::offset_of!(ProcessorControlRegion, gdt) % 8 != 0 {
panic!("PCR is incorrectly defined, GDT alignment is too small");
}
};
impl ProcessorControlRegion {
const fn new_partial_init(cpu_id: LogicalCpuId) -> Self {
Self {
self_ref: ptr::null_mut(),
user_rsp_tmp: 0,
gdt: BASE_GDT,
percpu: PercpuBlock::init(cpu_id),
_rsvd: Align([0; 2]),
tss: TaskStateSegment::new(),
_iobitmap: [0; IOBITMAP_SIZE as usize],
_all_ones: 0xFF,
}
}
}
pub unsafe fn pcr() -> *mut ProcessorControlRegion {
unsafe {
// Primitive benchmarking of RDFSBASE and RDGSBASE in userspace, appears to indicate that
// obtaining FSBASE/GSBASE using mov gs:[gs_self_ref] is faster than using the (probably
// microcoded) instructions.
let mut ret: *mut ProcessorControlRegion;
core::arch::asm!("mov {}, gs:[{}]", out(reg) ret, const(core::mem::offset_of!(ProcessorControlRegion, self_ref)));
ret
}
}
#[cfg(feature = "pti")]
pub unsafe fn set_tss_stack(pcr: *mut ProcessorControlRegion, stack: usize) {
use super::pti::{PTI_CONTEXT_STACK, PTI_CPU_STACK};
#[cfg(target_arch = "x86")]
unsafe {
core::ptr::addr_of_mut!((*pcr).tss.ss0).write((GDT_KERNEL_DATA << 3) as u16);
core::ptr::addr_of_mut!((*pcr).tss.esp0)
.write((PTI_CPU_STACK.as_ptr() as usize + PTI_CPU_STACK.len()) as u32);
}
#[cfg(target_arch = "x86_64")]
unsafe {
core::ptr::addr_of_mut!((*pcr).tss.rsp[0])
.write_unaligned((PTI_CPU_STACK.as_ptr() as usize + PTI_CPU_STACK.len()) as u64);
}
unsafe { PTI_CONTEXT_STACK = stack };
}
#[cfg(not(feature = "pti"))]
pub unsafe fn set_tss_stack(pcr: *mut ProcessorControlRegion, stack: usize) {
#[cfg(target_arch = "x86")]
unsafe {
core::ptr::addr_of_mut!((*pcr).tss.ss0).write((GDT_KERNEL_DATA << 3) as u16);
core::ptr::addr_of_mut!((*pcr).tss.esp0).write(stack as u32);
}
#[cfg(target_arch = "x86_64")]
unsafe {
// TODO: If this increases performance, read gs:[offset] directly
core::ptr::addr_of_mut!((*pcr).tss.rsp[0]).write_unaligned(stack as u64);
}
}
pub unsafe fn set_userspace_io_allowed(pcr: *mut ProcessorControlRegion, allowed: bool) {
let offset = if allowed {
u16::try_from(size_of::<TaskStateSegment>()).expect("guaranteed to fit in u16")
} else {
0xFFFF
};
unsafe {
#[cfg(target_arch = "x86")]
core::ptr::addr_of_mut!((*pcr).tss.iobp_offset).write(offset);
#[cfg(target_arch = "x86_64")]
core::ptr::addr_of_mut!((*pcr).tss.iomap_base).write(offset);
}
}
#[cold]
fn init_pcr(pcr: &mut ProcessorControlRegion, stack_end: usize) {
pcr.self_ref = pcr as *mut _;
// Setup the GDT.
#[cfg(target_arch = "x86")]
pcr.gdt[GDT_KERNEL_PERCPU].set_offset(pcr as *const _ as u32);
#[cfg(target_arch = "x86")]
{
pcr.tss.iobp_offset = 0xFFFF;
let tss = &pcr.tss as *const _ as usize as u32;
pcr.gdt[GDT_TSS].set_offset(tss);
pcr.gdt[GDT_TSS].set_limit(size_of::<TaskStateSegment>() as u32 + IOBITMAP_SIZE as u32);
}
#[cfg(target_arch = "x86_64")]
{
pcr.tss.iomap_base = 0xFFFF;
let tss = &mut pcr.tss as *mut TaskStateSegment as usize as u64;
let tss_lo = (tss & 0xFFFF_FFFF) as u32;
let tss_hi = (tss >> 32) as u32;
pcr.gdt[GDT_TSS].set_offset(tss_lo);
pcr.gdt[GDT_TSS].set_limit(size_of::<TaskStateSegment>() as u32 + IOBITMAP_SIZE);
// GDT is aligned to 8 bytes
#[expect(clippy::cast_ptr_alignment)]
unsafe {
(&mut pcr.gdt[GDT_TSS_HIGH] as *mut GdtEntry)
.cast::<u32>()
.write(tss_hi);
}
}
// Set the stack pointer to use when coming back from userspace.
unsafe {
set_tss_stack(pcr, stack_end);
}
}
#[cold]
pub unsafe fn install_pcr(pcr_ptr: *mut ProcessorControlRegion) {
let pcr = unsafe { &mut *pcr_ptr };
let gdtr: DescriptorTablePointer<SegmentDescriptor> = DescriptorTablePointer {
limit: const { (SEGMENT_COUNT * size_of::<GdtEntry>() - 1) as u16 },
base: pcr.gdt.as_ptr() as *const SegmentDescriptor,
};
// Load the new GDT, which is correctly located in thread local storage.
unsafe { dtables::lgdt(&gdtr) };
#[cfg(target_arch = "x86")]
unsafe {
// Reload the segment descriptors
segmentation::load_cs(SegmentSelector::new(GDT_KERNEL_CODE as u16, Ring::Ring0));
segmentation::load_ds(SegmentSelector::new(GDT_USER_DATA as u16, Ring::Ring3));
segmentation::load_es(SegmentSelector::new(GDT_USER_DATA as u16, Ring::Ring3));
segmentation::load_ss(SegmentSelector::new(GDT_KERNEL_DATA as u16, Ring::Ring0));
// TODO: Use FS for kernel percpu on i686?
segmentation::load_fs(SegmentSelector::new(GDT_USER_FS as u16, Ring::Ring0));
segmentation::load_gs(SegmentSelector::new(GDT_KERNEL_PERCPU as u16, Ring::Ring0));
}
#[cfg(target_arch = "x86_64")]
unsafe {
// Load segments again, possibly resetting FSBASE and GSBASE.
segmentation::load_cs(SegmentSelector::new(GDT_KERNEL_CODE as u16, Ring::Ring0));
segmentation::load_ss(SegmentSelector::new(GDT_KERNEL_DATA as u16, Ring::Ring0));
segmentation::load_ds(SegmentSelector::from_raw(0));
segmentation::load_es(SegmentSelector::from_raw(0));
segmentation::load_fs(SegmentSelector::from_raw(0));
// What happens when GS is loaded with a NULL selector, is undefined on Intel CPUs. However,
// GSBASE is set later.
segmentation::load_gs(SegmentSelector::from_raw(0));
// Ensure that GSBASE always points to the PCR in kernel space.
x86::msr::wrmsr(x86::msr::IA32_GS_BASE, pcr as *mut _ as usize as u64);
// While GSBASE points to the PCR in kernel space, userspace is free to set it to other values.
// Zero-initialize userspace's GSBASE. The reason the GSBASE register writes are reversed, is
// because entering usermode will entail executing the SWAPGS instruction.
x86::msr::wrmsr(x86::msr::IA32_KERNEL_GSBASE, 0);
// Set the userspace FSBASE to zero.
x86::msr::wrmsr(x86::msr::IA32_FS_BASE, 0);
}
// Load the task register
unsafe { task::load_tr(SegmentSelector::new(GDT_TSS as u16, Ring::Ring0)) };
unsafe { crate::percpu::init_tlb_shootdown(pcr.percpu.cpu_id, &mut pcr.percpu) };
}
/// Initialize GDT and configure percpu for the BSP.
#[cold]
pub unsafe fn init_bsp(stack_end: usize) {
static mut BSP_PCR: ProcessorControlRegion =
ProcessorControlRegion::new_partial_init(LogicalCpuId::BSP);
init_pcr(unsafe { &mut *ptr::addr_of_mut!(BSP_PCR) }, stack_end);
unsafe { install_pcr(ptr::addr_of_mut!(BSP_PCR)) };
}
#[cold]
pub fn allocate_and_init_pcr(
cpu_id: LogicalCpuId,
stack_end: usize,
) -> *mut ProcessorControlRegion {
let alloc_order = size_of::<ProcessorControlRegion>()
.div_ceil(PAGE_SIZE)
.next_power_of_two()
.trailing_zeros();
let pcr_frame = crate::memory::allocate_p2frame(alloc_order).expect("failed to allocate PCR");
let pcr_ptr = RmmA::phys_to_virt(pcr_frame.base()).data() as *mut ProcessorControlRegion;
unsafe { core::ptr::write(pcr_ptr, ProcessorControlRegion::new_partial_init(cpu_id)) };
init_pcr(unsafe { &mut *pcr_ptr }, stack_end);
pcr_ptr
}
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct GdtEntry {
pub limitl: u16,
pub offsetl: u16,
pub offsetm: u8,
pub access: u8,
pub flags_limith: u8,
pub offseth: u8,
}
impl GdtEntry {
pub const fn new(offset: u32, limit: u32, access: u8, flags: u8) -> Self {
GdtEntry {
limitl: limit as u16,
offsetl: offset as u16,
offsetm: (offset >> 16) as u8,
access,
flags_limith: flags & 0xF0 | ((limit >> 16) as u8) & 0x0F,
offseth: (offset >> 24) as u8,
}
}
#[cfg(target_arch = "x86")]
pub const fn offset(&self) -> u32 {
(self.offsetl as u32) | ((self.offsetm as u32) << 16) | ((self.offseth as u32) << 24)
}
pub const fn set_offset(&mut self, offset: u32) {
self.offsetl = offset as u16;
self.offsetm = (offset >> 16) as u8;
self.offseth = (offset >> 24) as u8;
}
pub const fn set_limit(&mut self, limit: u32) {
self.limitl = limit as u16;
self.flags_limith = self.flags_limith & 0xF0 | ((limit >> 16) as u8) & 0x0F;
}
}
impl PercpuBlock {
pub fn current() -> &'static Self {
unsafe { &*core::ptr::addr_of!((*pcr()).percpu) }
}
}
+361
View File
@@ -0,0 +1,361 @@
use core::{
cell::SyncUnsafeCell,
mem,
sync::atomic::{AtomicU32, Ordering},
};
use alloc::boxed::Box;
use hashbrown::{hash_map::DefaultHashBuilder, HashMap};
use x86::{
dtables::{self, DescriptorTablePointer},
segmentation::Descriptor as X86IdtEntry,
};
use crate::{
arch::{
interrupt::{
irq::{__generic_interrupts_end, __generic_interrupts_start},
*,
},
ipi::IpiKind,
},
cpu_set::LogicalCpuId,
memory::PAGE_SIZE,
};
use spin::RwLock;
#[repr(C)]
pub struct Idt {
pub(crate) entries: [IdtEntry; 256],
reservations: [AtomicU32; 8],
backup_stack_end: usize,
}
impl Idt {
const fn new() -> Self {
Self {
entries: [IdtEntry::new(); 256],
reservations: [const { AtomicU32::new(0) }; 8],
backup_stack_end: 0,
}
}
#[inline]
fn is_reserved(&self, index: u8) -> bool {
let byte_index = index / 32;
let bit = index % 32;
self.reservations[usize::from(byte_index)].load(Ordering::Acquire) & (1 << bit) != 0
}
#[inline]
pub(crate) fn set_reserved(&self, index: u8, reserved: bool) {
let byte_index = index / 32;
let bit = index % 32;
self.reservations[usize::from(byte_index)]
.fetch_or(u32::from(reserved) << bit, Ordering::AcqRel);
}
#[inline]
pub(crate) fn set_reserved_mut(&mut self, index: u8, reserved: bool) {
let byte_index = index / 32;
let bit = index % 32;
*{ &mut self.reservations[usize::from(byte_index)] }.get_mut() |=
u32::from(reserved) << bit;
}
}
// Allocate 64 KiB of stack space for the backup stack.
const BACKUP_STACK_SIZE: usize = PAGE_SIZE << 4;
static INIT_BSP_IDT: SyncUnsafeCell<Idt> = SyncUnsafeCell::new(Idt::new());
// TODO: VecMap?
pub(crate) static IDTS: RwLock<HashMap<LogicalCpuId, &'static mut Idt>> =
RwLock::new(HashMap::with_hasher(DefaultHashBuilder::new()));
#[inline]
pub fn is_reserved(cpu_id: LogicalCpuId, index: u8) -> bool {
if cpu_id == LogicalCpuId::BSP {
return unsafe { (*INIT_BSP_IDT.get()).is_reserved(index) };
}
IDTS.read().get(&cpu_id).unwrap().is_reserved(index)
}
#[inline]
pub fn set_reserved(cpu_id: LogicalCpuId, index: u8, reserved: bool) {
if cpu_id == LogicalCpuId::BSP {
unsafe { (*INIT_BSP_IDT.get()).set_reserved(index, reserved) };
return;
}
IDTS.read()
.get(&cpu_id)
.unwrap()
.set_reserved(index, reserved);
}
pub fn available_irqs_iter(cpu_id: LogicalCpuId) -> impl Iterator<Item = u8> + 'static {
(32..=254).filter(move |&index| !is_reserved(cpu_id, index))
}
fn set_exceptions(idt: &mut [IdtEntry]) {
// Set up exceptions
idt[0].set_func(exception::divide_by_zero);
idt[1].set_func(exception::debug);
idt[2].set_func(exception::non_maskable);
idt[3].set_func(exception::breakpoint);
idt[3].set_flags(IdtFlags::PRESENT | IdtFlags::RING_3 | IdtFlags::INTERRUPT);
idt[4].set_func(exception::overflow);
idt[5].set_func(exception::bound_range);
idt[6].set_func(exception::invalid_opcode);
idt[7].set_func(exception::device_not_available);
idt[8].set_func(exception::double_fault);
// 9 no longer available
idt[10].set_func(exception::invalid_tss);
idt[11].set_func(exception::segment_not_present);
idt[12].set_func(exception::stack_segment);
idt[13].set_func(exception::protection);
idt[14].set_func(exception::page);
// 15 reserved
idt[16].set_func(exception::fpu_fault);
idt[17].set_func(exception::alignment_check);
idt[18].set_func(exception::machine_check);
idt[19].set_func(exception::simd);
idt[20].set_func(exception::virtualization);
// 21 through 29 reserved
idt[30].set_func(exception::security);
// 31 reserved
}
/// Initializes a fully functional IDT for use before it be moved into the map. This is ONLY called
/// on the BSP, since the kernel heap is ready for the APs.
pub unsafe fn init_bsp() {
#[repr(C, packed(4096))]
struct BackupStack([u8; BACKUP_STACK_SIZE]);
static INIT_BSP_BACKUP_STACK: SyncUnsafeCell<BackupStack> =
SyncUnsafeCell::new(BackupStack([0; BACKUP_STACK_SIZE]));
unsafe {
init_generic(
LogicalCpuId::BSP,
&mut *INIT_BSP_IDT.get(),
INIT_BSP_BACKUP_STACK.get().addr() + BACKUP_STACK_SIZE,
);
install_idt(&mut *INIT_BSP_IDT.get());
}
}
pub fn allocate_and_init_idt(cpu_id: LogicalCpuId) -> *mut Idt {
let mut idts_btree = IDTS.write();
let idt = idts_btree
.entry(cpu_id)
.or_insert_with(|| Box::leak(Box::new(Idt::new())));
use crate::memory::{RmmA, RmmArch};
let frames = crate::memory::allocate_p2frame(4)
.expect("failed to allocate pages for backup interrupt stack");
// Physical pages are mapped linearly. So is the linearly mapped virtual memory.
let base_address = RmmA::phys_to_virt(frames.base());
// Stack always grows downwards.
let backup_stack_end = base_address.data() + BACKUP_STACK_SIZE;
init_generic(cpu_id, idt, backup_stack_end);
*idt
}
const BACKUP_IST: u8 = 1;
/// Initializes an IDT for any type of processor.
fn init_generic(cpu_id: LogicalCpuId, idt: &mut Idt, backup_stack_end: usize) {
let (current_idt, current_reservations) = (&mut idt.entries, &mut idt.reservations);
set_exceptions(current_idt);
// We give Non-Maskable Interrupts, Double Fault, and Machine Check exceptions separate
// stacks, since these (unless we are going to set up NMI watchdogs like Linux does) are
// considered the most fatal, especially Double Faults which are caused by errors __when
// accessing the system IDT__. If that goes wrong, then kernel memory may be partially
// corrupt, and we want a separate stack.
//
// Note that each CPU has its own "backup interrupt stack".
idt.backup_stack_end = backup_stack_end;
current_idt[2].set_ist(BACKUP_IST);
current_idt[8].set_ist(BACKUP_IST);
current_idt[18].set_ist(BACKUP_IST);
assert_eq!(
__generic_interrupts_end as usize - __generic_interrupts_start as usize,
224 * 8
);
for i in 0..224 {
current_idt[i + 32].set_func(unsafe {
mem::transmute::<usize, unsafe extern "C" fn()>(
__generic_interrupts_start as usize + i * 8,
)
});
}
// reserve bits 31:0, i.e. the first 32 interrupts, which are reserved for exceptions
*current_reservations[0].get_mut() |= 0x0000_0000_FFFF_FFFF;
if cpu_id == LogicalCpuId::BSP {
// Set up IRQs
current_idt[32].set_func(irq::pit_stack);
current_idt[33].set_func(irq::keyboard);
current_idt[34].set_func(irq::cascade);
current_idt[35].set_func(irq::com2);
current_idt[36].set_func(irq::com1);
current_idt[37].set_func(irq::lpt2);
current_idt[38].set_func(irq::floppy);
current_idt[39].set_func(irq::lpt1);
current_idt[40].set_func(irq::rtc);
current_idt[41].set_func(irq::pci1);
current_idt[42].set_func(irq::pci2);
current_idt[43].set_func(irq::pci3);
current_idt[44].set_func(irq::mouse);
current_idt[45].set_func(irq::fpu);
current_idt[46].set_func(irq::ata1);
current_idt[47].set_func(irq::ata2);
current_idt[48].set_func(irq::lapic_timer);
current_idt[49].set_func(irq::lapic_error);
// reserve bits 49:32, which are for the standard IRQs, and for the local apic timer and error.
*current_reservations[1].get_mut() |= 0x0003_FFFF;
} else {
// TODO: use_default_irqs! but also the legacy IRQs that are only needed on one CPU
current_idt[49].set_func(irq::lapic_error);
// reserve bit 49
*current_reservations[1].get_mut() |= 1 << 17;
}
// Set IPI handlers
current_idt[IpiKind::Wakeup as usize].set_func(ipi::wakeup);
current_idt[IpiKind::Switch as usize].set_func(ipi::switch);
current_idt[IpiKind::Tlb as usize].set_func(ipi::tlb);
current_idt[IpiKind::Pit as usize].set_func(ipi::pit);
idt.set_reserved_mut(IpiKind::Wakeup as u8, true);
idt.set_reserved_mut(IpiKind::Switch as u8, true);
idt.set_reserved_mut(IpiKind::Tlb as u8, true);
idt.set_reserved_mut(IpiKind::Pit as u8, true);
#[cfg(target_arch = "x86")]
{
let current_idt = &mut idt.entries;
// Set syscall function
current_idt[0x80].set_func(syscall::syscall);
current_idt[0x80].set_flags(IdtFlags::PRESENT | IdtFlags::RING_3 | IdtFlags::INTERRUPT);
idt.set_reserved_mut(0x80, true);
}
#[cfg(feature = "profiling")]
crate::profiling::maybe_setup_timer(idt, cpu_id);
}
pub unsafe fn install_idt(idt_ptr: *mut Idt) {
unsafe {
let idt = &mut *idt_ptr;
#[cfg(target_arch = "x86_64")] // TODO: x86
{
(*crate::arch::gdt::pcr()).tss.ist[usize::from(BACKUP_IST - 1)] =
idt.backup_stack_end as u64;
}
let idtr: DescriptorTablePointer<X86IdtEntry> = DescriptorTablePointer {
limit: (idt.entries.len() * size_of::<IdtEntry>() - 1) as u16,
base: idt.entries.as_ptr() as *const X86IdtEntry,
};
dtables::lidt(&idtr);
}
}
bitflags! {
pub struct IdtFlags: u8 {
const PRESENT = 1 << 7;
const RING_0 = 0 << 5;
const RING_1 = 1 << 5;
const RING_2 = 2 << 5;
const RING_3 = 3 << 5;
const SS = 1 << 4;
const INTERRUPT = 0xE;
const TRAP = 0xF;
}
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C, packed)]
pub struct IdtEntry {
offsetl: u16,
selector: u16,
zero: u8,
attribute: u8,
offsetm: u16,
#[cfg(target_arch = "x86_64")]
offseth: u32,
#[cfg(target_arch = "x86_64")]
_zero2: u32,
}
impl IdtEntry {
pub const fn new() -> IdtEntry {
IdtEntry {
offsetl: 0,
selector: 0,
zero: 0,
attribute: 0,
offsetm: 0,
#[cfg(target_arch = "x86_64")]
offseth: 0,
#[cfg(target_arch = "x86_64")]
_zero2: 0,
}
}
pub fn set_flags(&mut self, flags: IdtFlags) {
self.attribute = flags.bits();
}
pub fn set_ist(&mut self, ist: u8) {
assert_eq!(
ist & 0x07,
ist,
"interrupt stack table must be within 0..=7"
);
self.zero &= 0xF8;
self.zero |= ist;
}
pub fn set_offset(&mut self, selector: u16, base: usize) {
self.selector = selector;
self.offsetl = base as u16;
self.offsetm = (base >> 16) as u16;
#[cfg(target_arch = "x86_64")]
{
self.offseth = ((base as u64) >> 32) as u32;
}
}
// A function to set the offset more easily
pub fn set_func(&mut self, func: unsafe extern "C" fn()) {
self.set_flags(IdtFlags::PRESENT | IdtFlags::RING_0 | IdtFlags::INTERRUPT);
self.set_offset(
(crate::arch::gdt::GDT_KERNEL_CODE as u16) << 3,
func as usize,
);
}
}
+294
View File
@@ -0,0 +1,294 @@
use syscall::Exception;
use x86::irq::PageFaultError;
use crate::{
arch::x86_shared::interrupt,
context::signal::excp_handler,
memory::{GenericPfFlags, VirtualAddress},
ptrace,
sync::CleanLockToken,
syscall::flag::*,
};
interrupt_stack!(divide_by_zero, |stack| {
println!("Divide by zero");
stack.trace();
excp_handler(Exception {
kind: 0,
..Default::default()
});
});
interrupt_stack!(debug, @paranoid, |stack| {
let mut handled = false;
// Disable singlestep before there is a breakpoint, since the breakpoint
// handler might end up setting it again but unless it does we want the
// default to be false.
#[cfg(target_arch = "x86")]
let had_singlestep = stack.iret.eflags & (1 << 8) == 1 << 8;
#[cfg(target_arch = "x86_64")]
let had_singlestep = stack.iret.rflags & (1 << 8) == 1 << 8;
stack.set_singlestep(false);
let mut token = unsafe { CleanLockToken::new() };
if ptrace::breakpoint_callback(PTRACE_STOP_SINGLESTEP, None, &mut token).is_some() {
handled = true;
} else {
// There was no breakpoint, restore original value
stack.set_singlestep(had_singlestep);
}
if !handled {
println!("Debug trap");
stack.dump();
excp_handler(Exception {
kind: 1,
..Default::default()
});
}
});
interrupt_stack!(non_maskable, @paranoid, |stack| {
#[cfg(target_arch = "x86_64")]
unsafe { crate::profiling::nmi_handler(stack) };
#[cfg(not(all(target_arch = "x86_64", feature = "profiling")))]
{
// TODO: This will likely deadlock
println!("Non-maskable interrupt");
stack.dump();
}
});
interrupt_stack!(breakpoint, |stack| {
// The processor lets EIP/RIP point to the instruction *after* int3, so
// unhandled breakpoint interrupt don't go in an infinite loop. But we
// throw SIGTRAP anyway, so that's not a problem.
//
// We have the following code to prevent
// - RIP from going out of sync with instructions
// - The user having to do 2 syscalls to replace the instruction at RIP
// - Having more compatibility glue for GDB than necessary
//
// Let's just follow Linux convention and let RIP be RIP-1, point to the
// int3 instruction. After all, it's the sanest thing to do.
#[cfg(target_arch = "x86")]
{
stack.iret.eip -= 1;
}
#[cfg(target_arch = "x86_64")]
{
stack.iret.rip -= 1;
}
let mut token = unsafe { CleanLockToken::new() };
if ptrace::breakpoint_callback(PTRACE_STOP_BREAKPOINT, None, &mut token).is_none() {
println!("Breakpoint trap");
stack.dump();
excp_handler(Exception {
kind: 3,
..Default::default()
});
}
});
interrupt_stack!(overflow, |stack| {
println!("Overflow trap");
stack.trace();
excp_handler(Exception {
kind: 4,
..Default::default()
});
});
interrupt_stack!(bound_range, |stack| {
println!("Bound range exceeded fault");
stack.trace();
excp_handler(Exception {
kind: 5,
..Default::default()
});
});
interrupt_stack!(invalid_opcode, |stack| {
println!("Invalid opcode fault");
stack.trace();
excp_handler(Exception {
kind: 6,
..Default::default()
});
});
interrupt_stack!(device_not_available, |stack| {
println!("Device not available fault");
stack.trace();
excp_handler(Exception {
kind: 7,
..Default::default()
});
});
interrupt_error!(double_fault, |stack, _code| {
println!("Double fault");
stack.trace();
unsafe {
loop {
interrupt::disable();
interrupt::halt();
}
}
});
interrupt_error!(invalid_tss, |stack, code| {
println!("Invalid TSS fault");
stack.trace();
excp_handler(Exception {
kind: 10,
code,
..Default::default()
});
});
interrupt_error!(segment_not_present, |stack, code| {
println!("Segment not present fault");
stack.trace();
excp_handler(Exception {
kind: 11,
code,
..Default::default()
});
});
interrupt_error!(stack_segment, |stack, code| {
println!("Stack segment fault");
stack.trace();
excp_handler(Exception {
kind: 12,
code,
..Default::default()
});
});
interrupt_error!(protection, |stack, code| {
println!("Protection fault code={:#0x}", code);
stack.trace();
excp_handler(Exception {
kind: 13,
code,
..Default::default()
});
});
interrupt_error!(page, |stack, code| {
let cr2 = VirtualAddress::new(unsafe { x86::controlregs::cr2() });
let arch_flags = PageFaultError::from_bits_truncate(code as u32);
let mut generic_flags = GenericPfFlags::empty();
generic_flags.set(
GenericPfFlags::PRESENT,
arch_flags.contains(PageFaultError::P),
);
generic_flags.set(
GenericPfFlags::INVOLVED_WRITE,
arch_flags.contains(PageFaultError::WR),
);
generic_flags.set(
GenericPfFlags::USER_NOT_SUPERVISOR,
arch_flags.contains(PageFaultError::US),
);
generic_flags.set(
GenericPfFlags::INVL,
arch_flags.contains(PageFaultError::RSVD),
);
generic_flags.set(
GenericPfFlags::INSTR_NOT_DATA,
arch_flags.contains(PageFaultError::ID),
);
#[cfg(target_arch = "x86")]
if crate::memory::page_fault_handler(&mut stack.inner, generic_flags, cr2).is_err() {
println!("Page fault: {:>08X} {:#?}", cr2.data(), arch_flags);
stack.trace();
excp_handler(Exception {
kind: 14,
code,
address: cr2.data(),
});
}
#[cfg(target_arch = "x86_64")]
if crate::memory::page_fault_handler(stack, generic_flags, cr2).is_err() {
println!("Page fault: {:>016X} {:#?}", cr2.data(), arch_flags);
stack.trace();
excp_handler(Exception {
kind: 14,
code,
address: cr2.data(),
});
}
});
interrupt_stack!(fpu_fault, |stack| {
println!("FPU floating point fault");
stack.trace();
excp_handler(Exception {
kind: 16,
..Default::default()
});
});
interrupt_error!(alignment_check, |stack, code| {
println!("Alignment check fault");
stack.trace();
excp_handler(Exception {
kind: 17,
code,
..Default::default()
});
});
interrupt_stack!(machine_check, @paranoid, |stack| {
println!("Machine check fault");
stack.trace();
unsafe {
loop {
interrupt::disable();
interrupt::halt();
}
}
});
interrupt_stack!(simd, |stack| {
println!("SIMD floating point fault");
let mut mxcsr = 0_usize;
unsafe { core::arch::asm!("stmxcsr [{}]", in(reg) core::ptr::addr_of_mut!(mxcsr)) };
println!("MXCSR {:#0x}", mxcsr);
stack.trace();
excp_handler(Exception {
kind: 19,
..Default::default()
});
});
interrupt_stack!(virtualization, |stack| {
println!("Virtualization fault");
stack.trace();
unsafe {
loop {
interrupt::disable();
interrupt::halt();
}
}
});
interrupt_error!(security, |stack, _code| {
println!("Security exception");
stack.trace();
unsafe {
loop {
interrupt::disable();
interrupt::halt();
}
}
});
+28
View File
@@ -0,0 +1,28 @@
use crate::{
arch::device::local_apic::the_local_apic, context, percpu::PercpuBlock, sync::CleanLockToken,
};
interrupt!(wakeup, || {
unsafe { the_local_apic().eoi() };
});
interrupt!(tlb, || {
PercpuBlock::current().maybe_handle_tlb_shootdown();
unsafe { the_local_apic().eoi() };
});
interrupt!(switch, || {
unsafe { the_local_apic().eoi() };
let mut token = unsafe { CleanLockToken::new() };
let _ = context::switch(&mut token);
});
interrupt!(pit, || {
unsafe { the_local_apic().eoi() };
// Switch after a sufficient amount of time since the last switch.
let mut token = unsafe { CleanLockToken::new() };
context::switch::tick(&mut token);
});
+352
View File
@@ -0,0 +1,352 @@
use core::sync::atomic::{AtomicUsize, Ordering};
use alloc::vec::Vec;
use crate::{
arch::{
device::{
ioapic, local_apic, pic, pit,
serial::{COM1, COM2},
},
ipi::{ipi, IpiKind, IpiTarget},
},
context::{self, timeout},
percpu::PercpuBlock,
scheme::{irq::irq_trigger, serio::serio_input},
sync::CleanLockToken,
time,
};
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IrqMethod {
Pic = 0,
Apic = 1,
}
static SPURIOUS_COUNT_IRQ7: AtomicUsize = AtomicUsize::new(0);
static SPURIOUS_COUNT_IRQ15: AtomicUsize = AtomicUsize::new(0);
pub fn spurious_count_irq7() -> usize {
SPURIOUS_COUNT_IRQ7.load(Ordering::Relaxed)
}
pub fn spurious_count_irq15() -> usize {
SPURIOUS_COUNT_IRQ15.load(Ordering::Relaxed)
}
pub fn spurious_count() -> usize {
spurious_count_irq7() + spurious_count_irq15()
}
pub fn spurious_irq_resource(_token: &mut CleanLockToken) -> syscall::Result<Vec<u8>> {
match irq_method() {
IrqMethod::Apic => Ok(Vec::from(&b"(not implemented for APIC yet)"[..])),
IrqMethod::Pic => Ok(format!(
"{}\tIRQ7\n{}\tIRQ15\n{}\ttotal\n",
spurious_count_irq7(),
spurious_count_irq15(),
spurious_count()
)
.into_bytes()),
}
}
static IRQ_METHOD: AtomicUsize = AtomicUsize::new(IrqMethod::Pic as usize);
pub fn set_irq_method(method: IrqMethod) {
IRQ_METHOD.store(method as usize, core::sync::atomic::Ordering::Release);
}
fn irq_method() -> IrqMethod {
let raw = IRQ_METHOD.load(core::sync::atomic::Ordering::Acquire);
match raw {
0 => IrqMethod::Pic,
1 => IrqMethod::Apic,
_ => unreachable!(),
}
}
/// Notify the IRQ scheme that an IRQ has been registered. This should mask the IRQ until the
/// scheme user unmasks it ("acknowledges" it).
unsafe fn trigger(irq: u8) {
unsafe {
match irq_method() {
IrqMethod::Pic => {
if irq < 16 {
pic_mask(irq)
}
}
IrqMethod::Apic => ioapic_mask(irq),
}
let mut token = CleanLockToken::new();
irq_trigger(irq, &mut token);
}
}
/// Unmask the IRQ. This is called from the IRQ scheme, which does this when a user process has
/// processed the IRQ.
pub unsafe fn acknowledge(irq: usize) {
unsafe {
match irq_method() {
IrqMethod::Pic => {
if irq < 16 {
pic_unmask(irq)
}
}
IrqMethod::Apic => ioapic_unmask(irq),
}
}
}
/// Sends an end-of-interrupt, so that the interrupt controller can go on to the next one.
pub unsafe fn eoi(irq: u8) {
unsafe {
PercpuBlock::current().stats.add_irq(irq);
match irq_method() {
IrqMethod::Pic => {
if irq < 16 {
pic_eoi(irq)
}
}
IrqMethod::Apic => lapic_eoi(),
}
}
}
unsafe fn pic_mask(irq: u8) {
unsafe {
debug_assert!(irq < 16);
if irq >= 8 {
pic::slave().mask_set(irq - 8);
} else {
pic::master().mask_set(irq);
}
}
}
unsafe fn ioapic_mask(irq: u8) {
unsafe {
ioapic::mask(irq);
}
}
unsafe fn pic_eoi(irq: u8) {
unsafe {
debug_assert!(irq < 16);
if irq >= 8 {
pic::master().ack();
pic::slave().ack();
} else {
pic::master().ack();
}
}
}
unsafe fn lapic_eoi() {
unsafe { local_apic::the_local_apic().eoi() }
}
unsafe fn pic_unmask(irq: usize) {
unsafe {
debug_assert!(irq < 16);
if irq >= 8 {
pic::slave().mask_clear(irq as u8 - 8);
} else {
pic::master().mask_clear(irq as u8);
}
}
}
unsafe fn ioapic_unmask(irq: usize) {
unsafe {
ioapic::unmask(irq as u8);
}
}
interrupt_stack!(pit_stack, |_stack| {
// Saves CPU time by not sending IRQ event irq_trigger(0);
let mut token = unsafe { CleanLockToken::new() };
{
*time::OFFSET.write(token.token()) += pit::RATE;
}
unsafe { eoi(0) };
// Wake up other CPUs
ipi(IpiKind::Pit, IpiTarget::Other);
// Any better way of doing this?
timeout::trigger(&mut token);
// Switch after a sufficient amount of time since the last switch.
context::switch::tick(&mut token);
});
interrupt!(keyboard, || {
let data: u8;
unsafe { core::arch::asm!("in al, 0x60", out("al") data) };
unsafe { eoi(1) };
let mut token = unsafe { CleanLockToken::new() };
serio_input(0, data, &mut token);
});
interrupt!(cascade, || {
// No need to do any operations on cascade
unsafe { eoi(2) };
});
interrupt!(com2, || {
let mut token = unsafe { CleanLockToken::new() };
COM2.lock().receive(&mut token);
unsafe { eoi(3) };
});
interrupt!(com1, || {
let mut token = unsafe { CleanLockToken::new() };
COM1.lock().receive(&mut token);
unsafe { eoi(4) };
});
interrupt!(lpt2, || {
unsafe {
trigger(5);
eoi(5);
}
});
interrupt!(floppy, || {
unsafe {
trigger(6);
eoi(6);
}
});
interrupt!(lpt1, || {
unsafe {
if irq_method() == IrqMethod::Pic && pic::master().isr() & (1 << 7) == 0 {
// the IRQ was spurious, ignore it but increment a counter.
SPURIOUS_COUNT_IRQ7.fetch_add(1, Ordering::Relaxed);
return;
}
trigger(7);
eoi(7);
}
});
interrupt!(rtc, || {
unsafe {
trigger(8);
eoi(8);
}
});
interrupt!(pci1, || {
unsafe {
trigger(9);
eoi(9);
}
});
interrupt!(pci2, || {
unsafe {
trigger(10);
eoi(10);
}
});
interrupt!(pci3, || {
unsafe {
trigger(11);
eoi(11);
}
});
interrupt!(mouse, || {
let data: u8;
unsafe { core::arch::asm!("in al, 0x60", out("al") data) };
unsafe { eoi(12) };
let mut token = unsafe { CleanLockToken::new() };
serio_input(1, data, &mut token);
});
interrupt!(fpu, || {
unsafe {
trigger(13);
eoi(13);
}
});
interrupt!(ata1, || {
unsafe {
trigger(14);
eoi(14);
}
});
interrupt!(ata2, || {
unsafe {
if irq_method() == IrqMethod::Pic && pic::slave().isr() & (1 << 7) == 0 {
SPURIOUS_COUNT_IRQ15.fetch_add(1, Ordering::Relaxed);
pic::master().ack();
return;
}
trigger(15);
eoi(15);
}
});
interrupt!(lapic_timer, || {
println!("Local apic timer interrupt");
unsafe { lapic_eoi() };
});
#[cfg(feature = "profiling")]
interrupt!(aux_timer, || {
unsafe { lapic_eoi() };
crate::ipi::ipi(IpiKind::Profile, IpiTarget::Other);
});
interrupt!(lapic_error, || {
error!("Local apic internal error: ESR={:#0x}", unsafe {
local_apic::the_local_apic().esr()
});
unsafe { lapic_eoi() };
});
interrupt_error!(generic_irq, |_stack, code| {
let mut token = unsafe { CleanLockToken::new() };
// The reason why 128 is subtracted and added from the code, is that PUSH imm8 sign-extends the
// value, and the longer PUSH imm32 would make the generic_interrupts table twice as large
// (containing lots of useless NOPs).
irq_trigger((code as i32).wrapping_add(128) as u8, &mut token);
unsafe { lapic_eoi() };
});
core::arch::global_asm!("
.globl __generic_interrupts_start
.globl __generic_interrupts_end
.p2align 3
__generic_interrupts_start:
n = 0
.rept 224
push (n - 128)
jmp {}
.p2align 3
n = n + 1
.endr
__generic_interrupts_end:
", sym generic_irq);
unsafe extern "C" {
pub fn __generic_interrupts_start();
pub fn __generic_interrupts_end();
}
+44
View File
@@ -0,0 +1,44 @@
//! Interrupt instructions
pub mod exception;
pub mod ipi;
pub mod irq;
pub mod trace;
pub use super::idt::{available_irqs_iter, is_reserved, set_reserved};
/// Clear interrupts
#[inline(always)]
pub unsafe fn disable() {
unsafe {
core::arch::asm!("cli", options(nomem, nostack));
}
}
/// Set interrupts and halt
/// This will atomically wait for the next interrupt
/// Performing enable followed by halt is not guaranteed to be atomic, use this instead!
#[inline(always)]
pub unsafe fn enable_and_halt() {
unsafe {
core::arch::asm!("sti; hlt", options(nomem, nostack));
}
}
/// Set interrupts and nop
/// This will enable interrupts and allow the IF flag to be processed
/// Simply enabling interrupts does not guarantee that they will trigger, use this instead!
#[inline(always)]
pub unsafe fn enable_and_nop() {
unsafe {
core::arch::asm!("sti; nop", options(nomem, nostack));
}
}
/// Halt instruction
#[inline(always)]
pub unsafe fn halt() {
unsafe {
core::arch::asm!("hlt", options(nomem, nostack));
}
}

Some files were not shown because too many files have changed in this diff Show More