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:
@@ -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(®isters), 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) = ®isters.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());
|
||||
|
||||
Reference in New Issue
Block a user