feat: raw framebuffer fallback for fbbootlogd when DRM unavailable

- Add RawFb struct: direct framebuffer rendering via physmap
- Add RawTextScreen: simple text renderer using orbclient font
- Fallback in FbbootlogScheme::new() when V2GraphicsHandle fails
- Reads FRAMEBUFFER_ADDR/WIDTH/HEIGHT/STRIDE from bootloader env
- Scroll via ptr::copy on pixel rows, clear bottom line
- No DRM, no shadow buffer, no GPU required — like MS-DOS text mode
- Add common dependency to fbbootlogd Cargo.toml
This commit is contained in:
2026-05-17 14:56:50 +03:00
parent 20853c41f5
commit 2bfe4b427b
58 changed files with 1691 additions and 3602 deletions
@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! {
dtb::serial::init_early(dtb);
}
info!("RedBear OS starting...");
info!("Redox OS starting...");
args.print();
// Initialize RMM
@@ -97,7 +97,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! {
init_early(dtb);
}
info!("RedBear OS starting...");
info!("Redox OS starting...");
args.print();
if let Some(dtb) = &dtb {
@@ -14,10 +14,6 @@ pub struct IoApicRegs {
pointer: *const u32,
}
impl IoApicRegs {
fn redirection_index_valid(&mut self, idx: u8) -> bool {
idx <= self.max_redirection_table_entries()
}
fn ioregsel(&self) -> *const u32 {
self.pointer
}
@@ -48,28 +44,21 @@ impl IoApicRegs {
pub fn read_ioapicver(&mut self) -> u32 {
self.read_reg(0x01)
}
pub fn read_ioredtbl(&mut self, idx: u8) -> Option<u64> {
if !self.redirection_index_valid(idx) {
warn!("IOAPIC read_ioredtbl index {} out of range", idx);
return None;
}
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);
Some(u64::from(lo) | (u64::from(hi) << 32))
u64::from(lo) | (u64::from(hi) << 32)
}
pub fn write_ioredtbl(&mut self, idx: u8, value: u64) -> bool {
if !self.redirection_index_valid(idx) {
warn!("IOAPIC write_ioredtbl index {} out of range", idx);
return false;
}
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);
true
}
pub fn max_redirection_table_entries(&mut self) -> u8 {
@@ -103,37 +92,17 @@ impl IoApic {
}
/// 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) -> bool {
let Some(raw) = info.as_raw() else {
return false;
};
self.regs.lock().write_ioredtbl(idx, raw)
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 Some(mut reg) = guard.read_ioredtbl(idx) else {
return;
};
let mut reg = guard.read_ioredtbl(idx);
reg &= !(1 << 16);
reg |= u64::from(mask) << 16;
let _ = guard.write_ioredtbl(idx, reg);
}
/// Change the destination APIC for a GSI by reprogramming the redirection table entry.
/// Preserves all other fields (vector, polarity, trigger mode, delivery mode, mask).
/// Returns true if the entry was successfully updated.
pub fn set_irq_affinity(&self, gsi: u32, dest: ApicId) -> bool {
let idx = (gsi - self.gsi_start) as u8;
let mut guard = self.regs.lock();
let Some(mut entry) = guard.read_ioredtbl(idx) else {
return false;
};
// Clear destination field (bits 63:56 for xAPIC physical mode)
// and set new destination APIC ID
entry &= !(0xFF_u64 << 56);
entry |= u64::from(dest.get()) << 56;
guard.write_ioredtbl(idx, entry)
guard.write_ioredtbl(idx, reg);
}
}
@@ -180,26 +149,19 @@ pub struct MapInfo {
}
impl MapInfo {
pub fn as_raw(&self) -> Option<u64> {
if !(0x20..=0xFE).contains(&self.vector) {
warn!(
"Refusing to map IOAPIC vector outside valid range: {:#x}",
self.vector
);
return None;
}
pub fn as_raw(&self) -> u64 {
assert!(self.vector >= 0x20);
assert!(self.vector <= 0xFE);
// TODO: Check for reserved fields.
Some(
(u64::from(self.dest.get()) << 56)
(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),
)
| u64::from(self.vector)
}
}
@@ -213,7 +175,7 @@ impl fmt::Debug for IoApic {
let count = guard.max_redirection_table_entries();
f.debug_list()
.entries((0..=count).filter_map(|i| guard.read_ioredtbl(i)))
.entries((0..count).map(|i| guard.read_ioredtbl(i)))
.finish()
}
}
@@ -275,14 +237,11 @@ pub unsafe fn handle_ioapic(madt_ioapic: &'static MadtIoApic) {
let ioapic_registers = virt.data() as *const u32;
let ioapic = IoApic::new(ioapic_registers, madt_ioapic.gsi_base);
let detected_id = ioapic.regs.lock().id();
if detected_id != madt_ioapic.id {
warn!(
"mismatched ACPI MADT I/O APIC ID: MADT={}, IOAPIC={}; continuing with detected hardware",
madt_ioapic.id,
detected_id
);
}
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);
}
@@ -351,11 +310,11 @@ pub unsafe fn init() {
}
}
}
for ioapic in ioapics() {
for idx in 0..=ioapic.count {
ioapic.set_mask(ioapic.gsi_start + u32::from(idx), true);
}
}
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)
@@ -370,6 +329,7 @@ pub unsafe fn init() {
.iter()
.any(|over| over.bus_irq == legacy_irq)
{
// there's an IRQ conflict, making this legacy IRQ inaccessible.
continue;
}
(
@@ -389,6 +349,7 @@ pub unsafe fn init() {
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,
@@ -405,32 +366,7 @@ pub unsafe fn init() {
},
vector: 32 + legacy_irq,
};
if !apic.map(redir_tbl_index, map_info) {
warn!(
"Unable to map legacy IRQ {} (GSI {}) through IOAPIC index {}",
legacy_irq,
gsi,
redir_tbl_index
);
}
if legacy_irq == 0 && gsi != u32::from(legacy_irq) {
if let Some(apic0) = find_ioapic(u32::from(legacy_irq)) {
let idx0 = (u32::from(legacy_irq) - apic0.gsi_start) as u8;
let _ = apic0.map(
idx0,
MapInfo {
dest: bsp_apic_id,
dest_mode: DestinationMode::Physical,
delivery_mode: DeliveryMode::Fixed,
mask: false,
polarity: ApicPolarity::ActiveHigh,
trigger_mode: ApicTriggerMode::Edge,
vector: 32,
},
);
}
}
apic.map(redir_tbl_index, map_info);
}
println!(
"I/O APICs: {:?}, overrides: {:?}",
@@ -470,7 +406,7 @@ fn resolve(irq: u8) -> u32 {
fn find_ioapic(gsi: u32) -> Option<&'static IoApic> {
ioapics()
.iter()
.find(|apic| gsi >= apic.gsi_start && gsi <= apic.gsi_start + u32::from(apic.count))
.find(|apic| gsi >= apic.gsi_start && gsi < apic.gsi_start + u32::from(apic.count))
}
pub unsafe fn mask(irq: u8) {
@@ -489,14 +425,3 @@ pub unsafe fn unmask(irq: u8) {
};
apic.set_mask(gsi, false);
}
/// Change the destination CPU for an IRQ by reprogramming the IOAPIC redirection entry.
/// Resolves the legacy IRQ to its GSI, finds the owning IOAPIC, and updates the destination
/// APIC ID in the redirection table while preserving all other fields.
pub unsafe fn set_affinity(irq: u8, dest: ApicId) -> bool {
let gsi = resolve(irq);
match find_ioapic(gsi) {
Some(apic) => apic.set_irq_affinity(gsi, dest),
None => false,
}
}
@@ -0,0 +1,312 @@
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 {
info!("Detected xAPIC at {:#x}", physaddr.data());
self.address = map_device_memory(physaddr, 4096).data();
} else {
info!("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) >> 24 }
})
}
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 {
const PENDING: u32 = 1 << 12;
while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING {
core::hint::spin_loop();
}
wrmsr(IA32_X2APIC_ICR, value);
while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING {
core::hint::spin_loop();
}
}
} 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);
}
}
}
pub unsafe fn set_lvt_nmi(&mut self, pin: u8, flags: u16) {
let polarity = match flags & 0b11 {
0b11 => 1 << 13,
_ => 0,
};
let trigger_mode = match (flags >> 2) & 0b11 {
0b11 => 1 << 15,
_ => 0,
};
let lvt_value = (0b100 << 8) | polarity | trigger_mode;
unsafe {
match pin {
0 => {
if self.x2 {
wrmsr(IA32_X2APIC_LVT_LINT0, u64::from(lvt_value));
} else {
self.write(0x350, lvt_value);
}
}
1 => {
if self.x2 {
wrmsr(IA32_X2APIC_LVT_LINT1, u64::from(lvt_value));
} else {
self.write(0x360, lvt_value);
}
}
_ => {}
}
}
}
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,
}
@@ -0,0 +1,14 @@
--- src/arch/x86_shared/device/local_apic.rs
+++ src/arch/x86_shared/device/local_apic.rs
@@ -61,9 +61,9 @@
if !self.x2 {
- info!("Detected xAPIC at {:#x}", physaddr.data());
+ debug!("Detected xAPIC at {:#x}", physaddr.data());
self.address = map_device_memory(physaddr, 4096).data();
} else {
- info!("Detected x2APIC");
+ debug!("Detected x2APIC");
}
@@ -4,11 +4,9 @@ pub mod cpu;
pub mod hpet;
pub mod ioapic;
pub mod local_apic;
pub mod msi;
pub mod pic;
pub mod pit;
pub mod serial;
pub mod vector;
#[cfg(feature = "system76_ec_debug")]
pub mod system76_ec;
@@ -25,7 +23,8 @@ pub unsafe fn init() {
}
}
pub unsafe fn init_after_acpi() {
unsafe { ioapic::init() };
// this will disable the IOAPIC if needed.
//ioapic::init(mapper);
}
unsafe fn init_hpet() -> bool {
@@ -1,183 +0,0 @@
// MSI/MSI-X support for x86 — kernel-level message composition and validation
// Cross-referenced from Linux 7.0: arch/x86/kernel/apic/msi.c (391 lines)
use crate::arch::device::local_apic::ApicId;
pub const MSI_ADDRESS_BASE: u64 = 0xFEE0_0000;
pub const MSI_ADDRESS_MASK: u64 = 0xFEEF_F000;
const MSI_DEST_MODE_LOGICAL: u64 = 1 << 2;
const MSI_REDIRECTION_HINT: u64 = 1 << 3;
#[derive(Debug, Clone, Copy)]
pub struct MsiAddress {
pub raw: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct MsiData {
pub raw: u32,
}
#[derive(Debug, Clone)]
pub struct MsiMessage {
pub address: MsiAddress,
pub data: MsiData,
}
impl MsiAddress {
pub fn new(dest_apic_id: u8, redirection_hint: bool, dest_mode_logical: bool) -> Self {
let mut addr = MSI_ADDRESS_BASE;
addr |= u64::from(dest_apic_id) << 12;
if redirection_hint {
addr |= MSI_REDIRECTION_HINT;
}
if dest_mode_logical {
addr |= MSI_DEST_MODE_LOGICAL;
}
Self { raw: addr }
}
pub fn validate(addr: u64) -> bool {
(addr & MSI_ADDRESS_MASK) == MSI_ADDRESS_BASE
}
pub fn dest_apic_id(&self) -> u8 {
((self.raw >> 12) & 0xFF) as u8
}
}
impl MsiData {
pub fn new(vector: u8, delivery_mode: u8, trigger_mode: u8) -> Self {
let mut data = u32::from(vector);
data |= u32::from(delivery_mode) << 8;
data |= u32::from(trigger_mode) << 15;
Self { raw: data }
}
pub fn vector(&self) -> u8 {
(self.raw & 0xFF) as u8
}
pub fn delivery_mode(&self) -> u8 {
((self.raw >> 8) & 0x7) as u8
}
pub fn trigger_mode(&self) -> u8 {
((self.raw >> 15) & 0x1) as u8
}
}
impl MsiMessage {
pub fn compose(dest: ApicId, vector: u8, delivery_mode: u8, trigger_mode: u8) -> Self {
let address = MsiAddress::new(dest.get() as u8, false, false);
let data = MsiData::new(vector, delivery_mode, trigger_mode);
Self { address, data }
}
pub fn validate(&self) -> bool {
MsiAddress::validate(self.address.raw)
&& self.data.vector() >= 32
&& self.data.vector() < 255
}
}
pub fn is_valid_msi_address(addr: u64) -> bool {
MsiAddress::validate(addr)
}
pub fn is_valid_msi_vector(vector: u8) -> bool {
vector >= 32 && vector < 255
}
#[derive(Debug)]
pub struct MsiCapability {
pub msg_ctl: u16,
pub msg_addr_lo: u32,
pub msg_addr_hi: u32,
pub msg_data: u16,
pub mask_bits: u32,
pub pending_bits: u32,
pub is_64bit: bool,
pub is_maskable: bool,
pub multiple_message_capable: u8,
}
impl MsiCapability {
pub fn parse(raw: &[u32; 6], msg_ctl: u16) -> Self {
Self {
msg_ctl,
msg_addr_lo: raw[1],
msg_addr_hi: if msg_ctl & (1 << 7) != 0 { raw[2] } else { 0 },
msg_data: if msg_ctl & (1 << 7) != 0 {
(raw[3] & 0xFFFF) as u16
} else {
(raw[2] & 0xFFFF) as u16
},
mask_bits: if msg_ctl & (1 << 8) != 0 {
if msg_ctl & (1 << 7) != 0 {
raw[3] >> 16
} else {
raw[3]
}
} else {
0
},
pending_bits: if msg_ctl & (1 << 8) != 0 { raw[4] } else { 0 },
is_64bit: msg_ctl & (1 << 7) != 0,
is_maskable: msg_ctl & (1 << 8) != 0,
multiple_message_capable: ((msg_ctl >> 1) & 0x7) as u8,
}
}
}
#[derive(Debug)]
pub struct MsixCapability {
pub msg_ctl: u16,
pub table_offset: u32,
pub table_bar: u8,
pub pba_offset: u32,
pub pba_bar: u8,
pub table_size: u16,
}
impl MsixCapability {
pub fn parse(raw: &[u32; 3], msg_ctl: u16) -> Self {
Self {
msg_ctl,
table_offset: raw[1] & !0x7,
table_bar: (raw[1] & 0x7) as u8,
pba_offset: raw[2] & !0x7,
pba_bar: (raw[2] & 0x7) as u8,
table_size: ((msg_ctl >> 1) & 0x7FF) as u16 + 1,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compose_message() {
let msg = MsiMessage::compose(ApicId::new(3), 48, 0b101, 1);
assert!(msg.validate());
assert_eq!(msg.address.dest_apic_id(), 3);
assert_eq!(msg.data.vector(), 48);
assert_eq!(msg.data.delivery_mode(), 0b101);
assert_eq!(msg.data.trigger_mode(), 1);
}
#[test]
fn test_invalid_address() {
assert!(!is_valid_msi_address(0xDEAD_BEEF));
assert!(is_valid_msi_address(0xFEE0_0000));
}
#[test]
fn test_msi_parse() {
let raw = [0u32; 6];
let cap = MsiCapability::parse(&raw, 0);
assert!(!cap.is_64bit);
assert!(!cap.is_maskable);
}
}
@@ -1,53 +0,0 @@
use crate::cpu_set::LogicalCpuId;
const VECTOR_COUNT: usize = 224;
static VECTORS: [core::sync::atomic::AtomicU32; 7] = [
core::sync::atomic::AtomicU32::new(0),
core::sync::atomic::AtomicU32::new(0),
core::sync::atomic::AtomicU32::new(0),
core::sync::atomic::AtomicU32::new(0),
core::sync::atomic::AtomicU32::new(0),
core::sync::atomic::AtomicU32::new(0),
core::sync::atomic::AtomicU32::new(0),
];
pub fn allocate_vector(_cpu: LogicalCpuId) -> Option<u8> {
for (bank, slot) in VECTORS.iter().enumerate() {
let mut bits = slot.load(core::sync::atomic::Ordering::Acquire);
loop {
let free = bits.trailing_ones() as usize;
if free >= 32 {
break;
}
let bit = 1u32 << free;
match slot.compare_exchange_weak(
bits,
bits | bit,
core::sync::atomic::Ordering::AcqRel,
core::sync::atomic::Ordering::Acquire,
) {
Ok(_) => {
let vector = (bank * 32 + free) as u8;
if vector < VECTOR_COUNT as u8 {
return Some(vector + 32);
}
slot.fetch_and(!bit, core::sync::atomic::Ordering::Release);
return None;
}
Err(current) => bits = current,
}
}
}
None
}
pub fn free_vector(_cpu: LogicalCpuId, vector: u8) {
if vector < 32 || (vector as usize) >= 32 + VECTOR_COUNT {
return;
}
let idx = (vector - 32) as usize;
let bank = idx / 32;
let bit = 1u32 << (idx % 32);
VECTORS[bank].fetch_and(!bit, core::sync::atomic::Ordering::Release);
}
@@ -192,15 +192,6 @@ impl ProcessorControlRegion {
}
}
#[cold]
fn halt_pcr_init() -> ! {
println!("FATAL: failed to allocate physical memory for Processor Control Region");
println!("Processor startup cannot continue. Halting.");
loop {
core::hint::spin_loop();
}
}
pub unsafe fn pcr() -> *mut ProcessorControlRegion {
unsafe {
// Primitive benchmarking of RDFSBASE and RDGSBASE in userspace, appears to indicate that
@@ -384,10 +375,7 @@ pub fn allocate_and_init_pcr(
.next_power_of_two()
.trailing_zeros();
let pcr_frame = match crate::memory::allocate_p2frame(alloc_order) {
Some(frame) => frame,
None => halt_pcr_init(),
};
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)) };
@@ -78,15 +78,6 @@ static INIT_BSP_IDT: SyncUnsafeCell<Idt> = SyncUnsafeCell::new(Idt::new());
pub(crate) static IDTS: RwLock<HashMap<LogicalCpuId, &'static mut Idt>> =
RwLock::new(HashMap::with_hasher(DefaultHashBuilder::new()));
#[cold]
fn halt_idt_init() -> ! {
println!("FATAL: failed to allocate physical pages for backup interrupt stack");
println!("Interrupt setup cannot continue. Halting.");
loop {
core::hint::spin_loop();
}
}
#[inline]
pub fn is_reserved(cpu_id: LogicalCpuId, index: u8) -> bool {
if cpu_id == LogicalCpuId::BSP {
@@ -110,8 +101,6 @@ pub fn set_reserved(cpu_id: LogicalCpuId, index: u8, reserved: bool) {
}
pub fn available_irqs_iter(cpu_id: LogicalCpuId) -> impl Iterator<Item = u8> + 'static {
let count = (32..=254).filter(|&index| !is_reserved(cpu_id, index)).count();
info!("available_irqs_iter: cpu_id={} count={}", cpu_id.get(), count);
(32..=254).filter(move |&index| !is_reserved(cpu_id, index))
}
@@ -172,10 +161,8 @@ pub fn allocate_and_init_idt(cpu_id: LogicalCpuId) -> *mut Idt {
.or_insert_with(|| Box::leak(Box::new(Idt::new())));
use crate::memory::{RmmA, RmmArch};
let frames = match crate::memory::allocate_p2frame(4) {
Some(frames) => frames,
None => halt_idt_init(),
};
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());
@@ -1,5 +1,3 @@
use core::sync::atomic::{AtomicBool, Ordering};
use syscall::Exception;
use x86::irq::PageFaultError;
@@ -12,22 +10,6 @@ use crate::{
syscall::flag::*,
};
static NMI_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
unsafe fn nmi_raw_serial_write(bytes: &[u8]) {
use crate::syscall::io::{Io, Pio};
let mut com1 = Pio::<u8>::new(0x3F8);
let lsr = Pio::<u8>::new(0x3F8 + 5);
for &byte in bytes {
while lsr.read() & (1 << 5) == 0 {
core::hint::spin_loop();
}
com1.write(byte);
}
}
interrupt_stack!(divide_by_zero, |stack| {
println!("Divide by zero");
stack.trace();
@@ -73,35 +55,9 @@ interrupt_stack!(non_maskable, @paranoid, |stack| {
#[cfg(not(all(target_arch = "x86_64", feature = "profiling")))]
{
if NMI_IN_PROGRESS.swap(true, Ordering::SeqCst) {
return;
}
unsafe {
nmi_raw_serial_write(b"Non-maskable interrupt\n");
nmi_raw_serial_write(b" RIP: ");
#[cfg(target_arch = "x86")]
let instruction_pointer = u64::from(stack.iret.eip);
#[cfg(target_arch = "x86_64")]
let instruction_pointer = stack.iret.rip;
let mut buf = [0u8; 19];
buf[0] = b'0';
buf[1] = b'x';
for i in 0..16 {
let nibble = ((instruction_pointer >> (60 - i * 4)) & 0xF) as u8;
buf[2 + i] = if nibble < 10 {
b'0' + nibble
} else {
b'a' + nibble - 10
};
}
buf[18] = b'\n';
nmi_raw_serial_write(&buf);
}
NMI_IN_PROGRESS.store(false, Ordering::SeqCst);
// TODO: This will likely deadlock
println!("Non-maskable interrupt");
stack.dump();
}
});
@@ -28,8 +28,6 @@ pub mod pti;
/// Initialization and start function
pub mod start;
pub mod sleep;
/// Stop function
pub mod stop;
@@ -1,712 +0,0 @@
use alloc::{sync::Arc, vec::Vec};
use core::{
ptr::NonNull,
str::FromStr,
sync::atomic::{AtomicU32, Ordering},
};
use acpi_ext::{
aml::{namespace::AmlName, object::Object, Interpreter},
registers::FixedRegisters,
sdt::{facs::Facs, fadt::Fadt, SdtHeader},
AcpiTables, Handle, Handler, PhysicalMapping,
};
use spin::Mutex;
use syscall::error::{Error, EINVAL, EIO};
use x86::{segmentation::SegmentSelector, task, Ring};
use crate::{
acpi::ACPI_ROOT_INFO,
arch::interrupt,
memory::{
round_down_pages, round_up_pages, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA,
RmmArch, VirtualAddress, PAGE_SIZE,
},
syscall::io::{Io, Pio},
};
const ACPI_SLP_TYP_SHIFT: u16 = 10;
const ACPI_SLP_TYP_MASK: u16 = 0x1C00;
const ACPI_SLP_EN: u16 = 1 << 13;
const WAKE_TRAMPOLINE_PHYS: usize = 0x8000;
const SLEEP_RETURN_OK: usize = 0;
#[cfg(target_arch = "x86_64")]
static WAKE_TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/s3_wakeup"));
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
struct DescriptorTableRegister {
limit: u16,
base: u64,
}
#[repr(C, align(64))]
#[derive(Clone, Copy, Debug)]
struct FpuState {
bytes: [u8; 4096],
}
impl Default for FpuState {
fn default() -> Self {
Self { bytes: [0; 4096] }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SleepState {
S3,
S5,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SleepError {
UnsupportedArch,
MissingAcpi,
MissingFadt,
MissingFacs,
MissingSleepObject,
InvalidSleepObject,
UnsupportedPmControl,
UnsupportedAmlOperation,
SleepDidNotEnter,
}
impl SleepError {
fn code(self) -> usize {
match self {
Self::UnsupportedArch => EINVAL as usize,
Self::MissingAcpi
| Self::MissingFadt
| Self::MissingFacs
| Self::MissingSleepObject
| Self::UnsupportedAmlOperation => EIO as usize,
Self::InvalidSleepObject | Self::UnsupportedPmControl | Self::SleepDidNotEnter => {
EINVAL as usize
}
}
}
fn from_code(code: usize) -> Self {
match code as i32 {
x if x == EINVAL => Self::InvalidSleepObject,
_ => Self::MissingAcpi,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
struct SavedCpuContext {
entry_rsp: usize,
runtime_rsp: usize,
facs_address: usize,
cr0: usize,
cr2: usize,
cr3: usize,
cr4: usize,
rflags: usize,
gdtr: DescriptorTableRegister,
idtr: DescriptorTableRegister,
efer: u64,
fs_base: u64,
gs_base: u64,
kernel_gs_base: u64,
fpu: FpuState,
}
static SAVED_CONTEXT: Mutex<Option<SavedCpuContext>> = Mutex::new(None);
static AML_MUTEX_IDS: AtomicU32 = AtomicU32::new(1);
#[derive(Clone, Copy, Debug)]
struct SleepTypeData {
a: u16,
b: u16,
}
#[derive(Clone, Copy)]
struct KernelAcpiHandler;
impl KernelAcpiHandler {
fn map_range(physical_address: usize, size: usize) -> (*mut u8, usize) {
let map_base = round_down_pages(physical_address);
let map_offset = physical_address - map_base;
let mapped_length = round_up_pages(size + map_offset);
// SAFETY: The ACPI interpreter only requests firmware-described physical regions.
unsafe {
let mut mapper = KernelMapper::lock_rw();
for page_index in 0..mapped_length / PAGE_SIZE {
let (_, flush) = mapper
.map_linearly(
PhysicalAddress::new(map_base + page_index * PAGE_SIZE),
PageFlags::new(),
)
.expect("failed to linearly map ACPI physical region");
flush.flush();
}
}
let virtual_base = RmmA::phys_to_virt(PhysicalAddress::new(map_base)).data();
((virtual_base + map_offset) as *mut u8, mapped_length)
}
}
impl Handler for KernelAcpiHandler {
unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T> {
let (virtual_start, mapped_length) = Self::map_range(physical_address, size);
PhysicalMapping {
physical_start: physical_address,
virtual_start: NonNull::new(virtual_start.cast::<T>())
.expect("expected mapped ACPI virtual address to be non-null"),
region_length: size,
mapped_length,
handler: *self,
}
}
fn unmap_physical_region<T>(_region: &PhysicalMapping<Self, T>) {}
fn read_u8(&self, address: usize) -> u8 {
// SAFETY: AML system-memory accesses are byte-addressable firmware regions.
unsafe { core::ptr::read_volatile(RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *const u8) }
}
fn read_u16(&self, address: usize) -> u16 {
// SAFETY: AML system-memory accesses are word-addressable firmware regions.
unsafe {
core::ptr::read_volatile(RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *const u16)
}
}
fn read_u32(&self, address: usize) -> u32 {
// SAFETY: AML system-memory accesses are dword-addressable firmware regions.
unsafe {
core::ptr::read_volatile(RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *const u32)
}
}
fn read_u64(&self, address: usize) -> u64 {
// SAFETY: AML system-memory accesses are qword-addressable firmware regions.
unsafe {
core::ptr::read_volatile(RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *const u64)
}
}
fn write_u8(&self, address: usize, value: u8) {
// SAFETY: AML system-memory accesses are byte-addressable firmware regions.
unsafe {
core::ptr::write_volatile(RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *mut u8, value)
}
}
fn write_u16(&self, address: usize, value: u16) {
// SAFETY: AML system-memory accesses are word-addressable firmware regions.
unsafe {
core::ptr::write_volatile(
RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *mut u16,
value,
)
}
}
fn write_u32(&self, address: usize, value: u32) {
// SAFETY: AML system-memory accesses are dword-addressable firmware regions.
unsafe {
core::ptr::write_volatile(
RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *mut u32,
value,
)
}
}
fn write_u64(&self, address: usize, value: u64) {
// SAFETY: AML system-memory accesses are qword-addressable firmware regions.
unsafe {
core::ptr::write_volatile(
RmmA::phys_to_virt(PhysicalAddress::new(address)).data() as *mut u64,
value,
)
}
}
fn read_io_u8(&self, port: u16) -> u8 {
Pio::<u8>::new(port).read()
}
fn read_io_u16(&self, port: u16) -> u16 {
Pio::<u16>::new(port).read()
}
fn read_io_u32(&self, port: u16) -> u32 {
Pio::<u32>::new(port).read()
}
fn write_io_u8(&self, port: u16, value: u8) {
Pio::<u8>::new(port).write(value)
}
fn write_io_u16(&self, port: u16, value: u16) {
Pio::<u16>::new(port).write(value)
}
fn write_io_u32(&self, port: u16, value: u32) {
Pio::<u32>::new(port).write(value)
}
fn read_pci_u8(&self, _address: acpi_ext::PciAddress, _offset: u16) -> u8 {
0
}
fn read_pci_u16(&self, _address: acpi_ext::PciAddress, _offset: u16) -> u16 {
0
}
fn read_pci_u32(&self, _address: acpi_ext::PciAddress, _offset: u16) -> u32 {
0
}
fn write_pci_u8(&self, _address: acpi_ext::PciAddress, _offset: u16, _value: u8) {}
fn write_pci_u16(&self, _address: acpi_ext::PciAddress, _offset: u16, _value: u16) {}
fn write_pci_u32(&self, _address: acpi_ext::PciAddress, _offset: u16, _value: u32) {}
fn nanos_since_boot(&self) -> u64 {
0
}
fn stall(&self, microseconds: u64) {
for _ in 0..(microseconds.saturating_mul(64)) {
core::hint::spin_loop();
}
}
fn sleep(&self, milliseconds: u64) {
for _ in 0..(milliseconds.saturating_mul(64_000)) {
core::hint::spin_loop();
}
}
fn create_mutex(&self) -> Handle {
Handle(AML_MUTEX_IDS.fetch_add(1, Ordering::Relaxed))
}
fn acquire(&self, _mutex: Handle, _timeout: u16) -> Result<(), acpi_ext::aml::AmlError> {
Ok(())
}
fn release(&self, _mutex: Handle) {}
}
fn sleep_state_name(state: SleepState) -> &'static str {
match state {
SleepState::S3 => "\\_S3",
SleepState::S5 => "\\_S5",
}
}
fn encode_sleep_type(value: u16) -> u16 {
if value <= 0x7 {
value << ACPI_SLP_TYP_SHIFT
} else {
value & ACPI_SLP_TYP_MASK
}
}
fn load_interpreter() -> Result<(
Arc<FixedRegisters<KernelAcpiHandler>>,
PhysicalMapping<KernelAcpiHandler, Facs>,
Interpreter<KernelAcpiHandler>,
), SleepError> {
let root = *ACPI_ROOT_INFO.get().ok_or(SleepError::MissingAcpi)?;
let handler = KernelAcpiHandler;
// SAFETY: ACPI root info is captured from the firmware-provided, already validated root table.
let tables = unsafe {
AcpiTables::from_rsdt(handler, root.revision, root.root_sdt_address.data())
.map_err(|_| SleepError::MissingAcpi)?
};
let fadt = tables.find_table::<Fadt>().ok_or(SleepError::MissingFadt)?;
let registers = Arc::new(
FixedRegisters::new(&fadt, handler).map_err(|_| SleepError::UnsupportedPmControl)?,
);
let facs_address = fadt.facs_address().map_err(|_| SleepError::MissingFacs)?;
// SAFETY: The FADT-supplied FACS address is used exactly as described by the ACPI spec.
let facs = unsafe { handler.map_physical_region::<Facs>(facs_address, core::mem::size_of::<Facs>()) };
// SAFETY: The AML interpreter only needs an owned mapping of the same firmware FACS table.
let interpreter_facs = unsafe {
handler.map_physical_region::<Facs>(facs_address, core::mem::size_of::<Facs>())
};
let dsdt = tables.dsdt().map_err(|_| SleepError::MissingFadt)?;
let interpreter = Interpreter::new(handler, dsdt.revision, Arc::clone(&registers), Some(interpreter_facs));
// SAFETY: Each AML table mapping is owned by the interpreter during table loading.
unsafe {
let mapping = handler.map_physical_region::<SdtHeader>(dsdt.phys_address, dsdt.length as usize);
let stream = core::slice::from_raw_parts(
mapping.virtual_start.as_ptr().byte_add(core::mem::size_of::<SdtHeader>()) as *const u8,
dsdt.length as usize - core::mem::size_of::<SdtHeader>(),
);
interpreter
.load_table(stream)
.map_err(|_| SleepError::UnsupportedAmlOperation)?;
for ssdt in tables.ssdts() {
let mapping = handler.map_physical_region::<SdtHeader>(ssdt.phys_address, ssdt.length as usize);
let stream = core::slice::from_raw_parts(
mapping.virtual_start.as_ptr().byte_add(core::mem::size_of::<SdtHeader>()) as *const u8,
ssdt.length as usize - core::mem::size_of::<SdtHeader>(),
);
interpreter
.load_table(stream)
.map_err(|_| SleepError::UnsupportedAmlOperation)?;
}
}
Ok((registers, facs, interpreter))
}
fn sleep_type_data_from_interpreter(
interpreter: &Interpreter<KernelAcpiHandler>,
state: SleepState,
) -> Result<SleepTypeData, SleepError> {
let name = AmlName::from_str(sleep_state_name(state)).map_err(|_| SleepError::MissingSleepObject)?;
let object = interpreter
.evaluate(name, Vec::new())
.map_err(|_| SleepError::MissingSleepObject)?;
let Object::Package(package) = &*object else {
return Err(SleepError::InvalidSleepObject);
};
let Some(typa_object) = package.first() else {
return Err(SleepError::InvalidSleepObject);
};
let Some(typb_object) = package.get(1) else {
return Err(SleepError::InvalidSleepObject);
};
let Object::Integer(typa) = &**typa_object else {
return Err(SleepError::InvalidSleepObject);
};
let Object::Integer(typb) = &**typb_object else {
return Err(SleepError::InvalidSleepObject);
};
Ok(SleepTypeData {
a: encode_sleep_type(*typa as u16),
b: encode_sleep_type(*typb as u16),
})
}
fn sleep_type_data(state: SleepState) -> Result<SleepTypeData, SleepError> {
let (_registers, _facs, interpreter) = load_interpreter()?;
sleep_type_data_from_interpreter(&interpreter, state)
}
fn install_wake_trampoline(stack_rsp: usize, cr3: usize) {
let trampoline_page = Page::containing_address(VirtualAddress::new(WAKE_TRAMPOLINE_PHYS));
let trampoline_frame = PhysicalAddress::new(WAKE_TRAMPOLINE_PHYS);
// SAFETY: The 0x8000 low-memory trampoline page is reserved by the kernel for bootstrap stubs.
let (result, _) = unsafe {
let mut mapper = KernelMapper::lock_rw();
let result = mapper
.map_phys(
trampoline_page.start_address(),
trampoline_frame,
PageFlags::new().execute(true).write(true),
)
.expect("failed to map S3 wake trampoline page");
(result, mapper.table().phys().data())
};
result.flush();
for (index, value) in WAKE_TRAMPOLINE_DATA.iter().enumerate() {
// SAFETY: The trampoline page is mapped writable at the same virtual address as the physical page.
unsafe {
core::ptr::write_volatile((WAKE_TRAMPOLINE_PHYS as *mut u8).add(index), *value);
}
}
// SAFETY: The wake trampoline layout reserves three qword fields immediately after the jump.
unsafe {
let stack_slot = (WAKE_TRAMPOLINE_PHYS + 8) as *mut u64;
let page_table_slot = stack_slot.add(1);
let code_slot = stack_slot.add(2);
stack_slot.write(stack_rsp as u64);
page_table_slot.write(cr3 as u64);
#[expect(clippy::fn_to_numeric_cast)]
code_slot.write(resume_from_s3_trampoline as usize as u64);
}
// SAFETY: The trampoline mapping is no longer needed once the physical page has been populated.
let (_frame, _, flush) = unsafe {
KernelMapper::lock_rw()
.unmap_phys(trampoline_page.start_address())
.expect("failed to unmap S3 wake trampoline page")
};
flush.flush();
}
fn save_descriptor_tables(context: &mut SavedCpuContext) {
// SAFETY: SGDT/SIDT only read the current CPU descriptor-table registers into the provided storage.
unsafe {
core::arch::asm!("sgdt [{}]", in(reg) &mut context.gdtr, options(nostack, preserves_flags));
core::arch::asm!("sidt [{}]", in(reg) &mut context.idtr, options(nostack, preserves_flags));
}
}
fn save_fpu_state(context: &mut SavedCpuContext) {
// SAFETY: The kernel owns the current CPU at suspend entry and the FXSAVE buffer is 64-byte aligned.
unsafe {
core::arch::asm!(
"fxsave64 [{}]",
in(reg) context.fpu.bytes.as_mut_ptr(),
);
}
}
fn restore_fpu_state(context: &SavedCpuContext) {
// SAFETY: The saved FXSAVE image belongs to the same CPU context and matches the restore instruction.
unsafe {
core::arch::asm!(
"fxrstor64 [{}]",
in(reg) context.fpu.bytes.as_ptr(),
);
}
}
fn save_cpu_context(entry_rsp: usize) -> SavedCpuContext {
let mut context = SavedCpuContext {
entry_rsp,
..SavedCpuContext::default()
};
// SAFETY: Reading control registers and MSRs is required to reconstruct the CPU execution state on wake.
unsafe {
core::arch::asm!(
"mov {}, cr0",
out(reg) context.cr0,
options(nostack, preserves_flags)
);
core::arch::asm!(
"mov {}, cr2",
out(reg) context.cr2,
options(nostack, preserves_flags)
);
core::arch::asm!(
"mov {}, cr3",
out(reg) context.cr3,
options(nostack, preserves_flags)
);
core::arch::asm!(
"mov {}, cr4",
out(reg) context.cr4,
options(nostack, preserves_flags)
);
core::arch::asm!(
"pushfq",
"pop {}",
out(reg) context.rflags,
options(preserves_flags)
);
core::arch::asm!("mov {}, rsp", out(reg) context.runtime_rsp, options(nostack, preserves_flags));
context.efer = x86::msr::rdmsr(x86::msr::IA32_EFER);
context.fs_base = x86::msr::rdmsr(x86::msr::IA32_FS_BASE);
context.gs_base = x86::msr::rdmsr(x86::msr::IA32_GS_BASE);
context.kernel_gs_base = x86::msr::rdmsr(x86::msr::IA32_KERNEL_GSBASE);
}
save_descriptor_tables(&mut context);
save_fpu_state(&mut context);
context
}
fn set_firmware_waking_vector(facs: &mut PhysicalMapping<KernelAcpiHandler, Facs>, vector: usize) {
facs.firmware_waking_vector = vector as u32;
facs.x_firmware_waking_vector = vector as u64;
}
fn write_pm1_control_block(
registers: &FixedRegisters<KernelAcpiHandler>,
sleep_type: SleepTypeData,
) -> Result<(), SleepError> {
let current_a = registers
.pm1_control_registers
.pm1a
.read()
.map_err(|_| SleepError::UnsupportedPmControl)? as u16;
let armed_a = (current_a & !(ACPI_SLP_TYP_MASK | ACPI_SLP_EN)) | sleep_type.a;
registers
.pm1_control_registers
.pm1a
.write(u64::from(armed_a))
.map_err(|_| SleepError::UnsupportedPmControl)?;
if let Some(pm1b) = &registers.pm1_control_registers.pm1b {
let current_b = pm1b.read().map_err(|_| SleepError::UnsupportedPmControl)? as u16;
let armed_b = (current_b & !(ACPI_SLP_TYP_MASK | ACPI_SLP_EN)) | sleep_type.b;
pm1b.write(u64::from(armed_b))
.map_err(|_| SleepError::UnsupportedPmControl)?;
pm1b.write(u64::from(armed_b | ACPI_SLP_EN))
.map_err(|_| SleepError::UnsupportedPmControl)?;
}
// SAFETY: WBINVD is required here to flush dirty cache lines before firmware powers down the CPU package.
unsafe {
core::arch::asm!("wbinvd", options(nostack, preserves_flags));
}
registers
.pm1_control_registers
.pm1a
.write(u64::from(armed_a | ACPI_SLP_EN))
.map_err(|_| SleepError::UnsupportedPmControl)?;
Ok(())
}
#[unsafe(naked)]
unsafe extern "sysv64" fn enter_sleep_raw(state: usize) -> usize {
core::arch::naked_asm!(
"mov rsi, rsp",
"jmp {inner}",
inner = sym enter_sleep_raw_inner,
);
}
extern "C" fn enter_sleep_raw_inner(state: usize, entry_rsp: usize) -> usize {
let state = match state {
3 => SleepState::S3,
5 => SleepState::S5,
_ => return SleepError::InvalidSleepObject.code(),
};
let (registers, mut facs, interpreter) = match load_interpreter() {
Ok(tuple) => tuple,
Err(error) => return error.code(),
};
let sleep_type = match sleep_type_data_from_interpreter(&interpreter, state) {
Ok(data) => data,
Err(error) => return error.code(),
};
let mut context = save_cpu_context(entry_rsp);
context.facs_address = facs.physical_start;
install_wake_trampoline(context.runtime_rsp, context.cr3);
set_firmware_waking_vector(&mut facs, WAKE_TRAMPOLINE_PHYS);
{
let mut saved = SAVED_CONTEXT.lock();
*saved = Some(context);
}
// SAFETY: Suspend entry must not be interrupted while the wake vector and PM1 control block are being armed.
unsafe {
interrupt::disable();
}
if let Err(error) = write_pm1_control_block(registers.as_ref(), sleep_type) {
return error.code();
}
// SAFETY: The final CLI+HLT sequence is the architectural handoff point after asserting SLP_EN.
unsafe {
core::arch::asm!("cli; hlt", options(nostack));
}
SleepError::SleepDidNotEnter.code()
}
extern "C" fn resume_from_s3_trampoline() -> ! {
let mut saved = SAVED_CONTEXT.lock();
let context = saved.take().expect("S3 wake trampoline resumed without saved CPU context");
drop(saved);
// SAFETY: The saved FACS physical address was captured from the validated FADT during suspend entry.
if context.facs_address != 0 {
let mut facs = unsafe {
KernelAcpiHandler.map_physical_region::<Facs>(
context.facs_address,
core::mem::size_of::<Facs>(),
)
};
set_firmware_waking_vector(&mut facs, 0);
}
// SAFETY: The wake trampoline already switched to the saved kernel CR3 and long mode, so the remaining restores are architectural register state only.
unsafe {
x86::msr::wrmsr(x86::msr::IA32_EFER, context.efer);
core::arch::asm!("mov cr3, {}", in(reg) context.cr3, options(nostack));
core::arch::asm!("mov cr4, {}", in(reg) context.cr4, options(nostack));
core::arch::asm!("mov cr2, {}", in(reg) context.cr2, options(nostack));
core::arch::asm!("mov cr0, {}", in(reg) context.cr0, options(nostack));
core::arch::asm!("lgdt [{}]", in(reg) &context.gdtr, options(nostack));
core::arch::asm!("lidt [{}]", in(reg) &context.idtr, options(nostack));
task::load_tr(SegmentSelector::new(crate::arch::gdt::GDT_TSS as u16, Ring::Ring0));
x86::msr::wrmsr(x86::msr::IA32_FS_BASE, context.fs_base);
x86::msr::wrmsr(x86::msr::IA32_GS_BASE, context.gs_base);
x86::msr::wrmsr(x86::msr::IA32_KERNEL_GSBASE, context.kernel_gs_base);
}
restore_fpu_state(&context);
// SAFETY: Returning with the original entry stack and RFLAGS completes the suspend call as a successful function return.
unsafe {
core::arch::asm!(
"mov rsp, {entry_rsp}",
"push {rflags}",
"popfq",
"xor eax, eax",
"ret",
entry_rsp = in(reg) context.entry_rsp,
rflags = in(reg) context.rflags,
options(noreturn)
);
}
}
pub fn enter_sleep_state(state: SleepState) -> core::result::Result<(), SleepError> {
#[cfg(not(target_arch = "x86_64"))]
{
let _ = state;
return Err(SleepError::UnsupportedArch);
}
#[cfg(target_arch = "x86_64")]
{
let raw = unsafe {
enter_sleep_raw(match state {
SleepState::S3 => 3,
SleepState::S5 => 5,
})
};
if raw == SLEEP_RETURN_OK {
Ok(())
} else {
Err(SleepError::from_code(raw))
}
}
}
pub fn available_sleep_states() -> &'static [u8] {
if sleep_type_data(SleepState::S3).is_ok() {
b"S3\nS5\n"
} else {
b"S5\n"
}
}
pub fn trigger_sleep_request(request: &str) -> Result<(), Error> {
match request.trim() {
"S3" => enter_sleep_state(SleepState::S3).map_err(|_| Error::new(EIO)),
"S5" => enter_sleep_state(SleepState::S5).map_err(|_| Error::new(EIO)),
_ => Err(Error::new(EINVAL)),
}
}
@@ -82,15 +82,6 @@ extern "C" fn kstart() {
/// The entry to Rust, all things must be initialized
unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
unsafe {
// EARLY CANARY: write 'R' to COM1 before any kernel init.
// This proves the serial hardware works and the kernel reached Rust entry.
// If this character appears but "RedBear OS starting..." does not,
// the hang is in args_ptr.read(), serial::init(), or graphical_debug::init().
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'R', options(nostack, preserves_flags));
}
let bootstrap = {
let args = args_ptr.read();
@@ -100,49 +91,27 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Set up graphical debug
graphical_debug::init(args.env());
// SECOND CANARY: write 'S' to COM1 after serial init.
// If 'R' appears but 'S' does not, the hang is in serial::init() or graphical_debug::init().
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'S', options(nostack, preserves_flags));
}
info!("RedBear OS starting...");
info!("Redox OS starting...");
args.print();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'1', options(nostack, preserves_flags)); }
// Set up GDT
gdt::init_bsp(stack_end);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'2', options(nostack, preserves_flags)); }
// Set up IDT
idt::init_bsp();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'3', options(nostack, preserves_flags)); }
// Initialize RMM
#[cfg(target_arch = "x86")]
crate::startup::memory::init(&args, Some(0x100000), Some(0x40000000));
#[cfg(target_arch = "x86_64")]
crate::startup::memory::init(&args, Some(0x100000), None);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'4', options(nostack, preserves_flags)); }
// Initialize paging
paging::init();
#[cfg(target_arch = "x86_64")]
crate::arch::alternative::early_init(true);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'5', options(nostack, preserves_flags)); }
// Set up syscall instruction
interrupt::syscall::init();
@@ -152,9 +121,6 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Activate memory logging
crate::log::init();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'6', options(nostack, preserves_flags)); }
// Initialize miscellaneous processor features
#[cfg(target_arch = "x86_64")]
crate::arch::misc::init(LogicalCpuId::BSP);
@@ -162,9 +128,6 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Initialize devices
device::init();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'7', options(nostack, preserves_flags)); }
// Read ACPI tables, starts APs
if cfg!(feature = "acpi") {
crate::acpi::init(args.acpi_rsdp());