diff --git a/src/acpi/aml/namespace.rs b/src/acpi/aml/namespace.rs index c420c9d..8716398 100644 --- a/src/acpi/aml/namespace.rs +++ b/src/acpi/aml/namespace.rs @@ -14,7 +14,7 @@ use super::AmlError; use crate::acpi::{SdtSignature, get_signature_from_index, get_index_from_signature}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum FieldSelector { Region(String), Bank { @@ -28,7 +28,7 @@ pub enum FieldSelector { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum ObjectReference { ArgObj(u8), LocalObj(u8), @@ -36,7 +36,7 @@ pub enum ObjectReference { Index(Box, Box) } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Method { pub arg_count: u8, pub serialized: bool, @@ -44,14 +44,14 @@ pub struct Method { pub term_list: Vec } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct BufferField { pub source_buf: Box, pub index: Box, pub length: Box } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FieldUnit { pub selector: FieldSelector, pub connection: Box, @@ -60,19 +60,19 @@ pub struct FieldUnit { pub length: usize } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Device { pub obj_list: Vec, pub notify_methods: BTreeMap> } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ThermalZone { pub obj_list: Vec, pub notify_methods: BTreeMap> } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Processor { pub proc_id: u8, pub p_blk: Option, @@ -80,7 +80,7 @@ pub struct Processor { pub notify_methods: BTreeMap> } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct OperationRegion { pub region: RegionSpace, pub offset: Box, @@ -89,13 +89,14 @@ pub struct OperationRegion { pub accessed_by: Option } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct PowerResource { pub system_level: u8, pub resource_order: u16, pub obj_list: Vec } +#[derive(Debug)] pub struct Accessor { pub read: fn(usize) -> u64, pub write: fn(usize, u64) @@ -110,7 +111,7 @@ impl Clone for Accessor { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum AmlValue { None, Uninitialized, @@ -136,10 +137,6 @@ pub enum AmlValue { ThermalZone(ThermalZone) } -impl Debug for AmlValue { - fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> { Ok(()) } -} - impl AmlValue { pub fn get_type_string(&self) -> String { match *self { diff --git a/src/acpi/madt.rs b/src/acpi/madt.rs index d890489..6774358 100644 --- a/src/acpi/madt.rs +++ b/src/acpi/madt.rs @@ -15,13 +15,16 @@ use crate::interrupt; use crate::start::{kstart_ap, CPU_COUNT, AP_READY}; /// The Multiple APIC Descriptor Table -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct Madt { sdt: &'static Sdt, pub local_address: u32, pub flags: u32 } +pub static mut MADT: Option = None; +pub const FLAG_PCAT: u32 = 1; + impl Madt { pub fn init(active_table: &mut ActivePageTable) { let madt_sdt = find_sdt("APIC"); @@ -34,6 +37,9 @@ impl Madt { }; if let Some(madt) = madt { + // safe because no APs have been started yet. + unsafe { MADT = Some(madt) }; + println!(" APIC: {:>08X}: {}", madt.local_address, madt.flags); let local_apic = unsafe { &mut LOCAL_APIC }; diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs index 1686835..5f507b2 100644 --- a/src/acpi/mod.rs +++ b/src/acpi/mod.rs @@ -31,18 +31,18 @@ use self::aml::{parse_aml_table, AmlError, AmlValue}; pub mod hpet; mod dmar; mod fadt; -mod madt; +pub mod madt; mod rsdt; -mod sdt; +pub mod sdt; mod xsdt; -mod aml; +pub mod aml; mod rxsdt; mod rsdp; const TRAMPOLINE: usize = 0x7E00; const AP_STARTUP: usize = TRAMPOLINE + 512; -fn get_sdt(sdt_address: usize, active_table: &mut ActivePageTable) -> &'static Sdt { +pub fn get_sdt(sdt_address: usize, active_table: &mut ActivePageTable) -> &'static Sdt { { let page = Page::containing_address(VirtualAddress::new(sdt_address)); if active_table.translate_page(page).is_none() { @@ -114,7 +114,7 @@ fn init_namespace() { } /// Parse the ACPI tables to gather CPU, interrupt, and timer information -pub unsafe fn init(active_table: &mut ActivePageTable) { +pub unsafe fn init(active_table: &mut ActivePageTable, already_supplied_rsdps: Option<(u64, u64)>) { { let mut sdt_ptrs = SDT_POINTERS.write(); *sdt_ptrs = Some(BTreeMap::new()); @@ -126,7 +126,8 @@ pub unsafe fn init(active_table: &mut ActivePageTable) { } // Search for RSDP - if let Some(rsdp) = RSDP::get_rsdp(active_table) { + if let Some(rsdp) = RSDP::get_rsdp(active_table, already_supplied_rsdps) { + println!("RSDP: {:?}", rsdp); let rxsdt = get_sdt(rsdp.sdt_address(), active_table); for &c in rxsdt.signature.iter() { @@ -192,7 +193,7 @@ pub fn set_global_s_state(state: u8) { } } -type SdtSignature = (String, [u8; 6], [u8; 8]); +pub type SdtSignature = (String, [u8; 6], [u8; 8]); pub static SDT_POINTERS: RwLock>> = RwLock::new(None); pub static SDT_ORDER: RwLock>> = RwLock::new(None); diff --git a/src/acpi/rsdp.rs b/src/acpi/rsdp.rs index 6ad61d8..fa244fd 100644 --- a/src/acpi/rsdp.rs +++ b/src/acpi/rsdp.rs @@ -1,3 +1,6 @@ +use core::convert::TryFrom; +use core::mem; + use crate::memory::Frame; use crate::paging::{ActivePageTable, Page, PhysicalAddress, VirtualAddress}; use crate::paging::entry::EntryFlags; @@ -18,8 +21,67 @@ pub struct RSDP { } impl RSDP { + fn is_acpi_1_0(&self) -> bool { + self.revision == 0 + } + fn is_acpi_2_0(&self) -> bool { + self.revision == 2 + } + fn get_already_supplied_rsdps(area: &[u8]) -> Option { + // the bootloader has already checked all the checksums for us, but we still need to + // double-check. + struct Iter<'a> { + buf: &'a [u8], + } + impl<'a> Iterator for Iter<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if self.buf.len() < 4 { return None } + + let length_bytes = <[u8; 4]>::try_from(&self.buf[..4]).ok()?; + let length = u32::from_ne_bytes(length_bytes) as usize; + + if (4 + length as usize) > self.buf.len() { return None } + + let buf = &self.buf[4..4 + length]; + self.buf = &self.buf[4 + length..]; + + Some(buf) + } + } + fn slice_to_rsdp(slice: &[u8]) -> Option<&RSDP> { + let ptr = slice.as_ptr() as usize; + + if slice.len() >= mem::size_of::() && ptr & (!0x7) == ptr { + let rsdp = unsafe { &*(slice.as_ptr() as *const RSDP) }; + // TODO: Validate + Some(rsdp) + } else { None } + } + + // first, find an RSDP for ACPI 2.0 + if let Some(rsdp_2_0) = (Iter { buf: area }.filter_map(slice_to_rsdp).find(|rsdp| rsdp.is_acpi_2_0())) { + return Some(*rsdp_2_0); + } + + // secondly, find an RSDP for ACPI 1.0 + if let Some(rsdp_1_0) = (Iter { buf: area }.filter_map(slice_to_rsdp).find(|rsdp| rsdp.is_acpi_1_0())) { + return Some(*rsdp_1_0); + } + + None + } + pub fn get_rsdp(active_table: &mut ActivePageTable, already_supplied_rsdps: Option<(u64, u64)>) -> Option { + if let Some((base, size)) = already_supplied_rsdps { + let area = unsafe { core::slice::from_raw_parts(base as usize as *const u8, size as usize) }; + Self::get_already_supplied_rsdps(area).or_else(|| Self::get_rsdp_by_searching(active_table)) + } else { + Self::get_rsdp_by_searching(active_table) + } + } /// Search for the RSDP - pub fn get_rsdp(active_table: &mut ActivePageTable) -> Option { + pub fn get_rsdp_by_searching(active_table: &mut ActivePageTable) -> Option { let start_addr = 0xE_0000; let end_addr = 0xF_FFFF; diff --git a/src/arch/x86_64/device/ioapic.rs b/src/arch/x86_64/device/ioapic.rs new file mode 100644 index 0000000..99f299e --- /dev/null +++ b/src/arch/x86_64/device/ioapic.rs @@ -0,0 +1,398 @@ +use core::{fmt, ptr}; + +use alloc::vec::Vec; +use spin::Mutex; + +#[cfg(feature = "acpi")] +use crate::acpi::madt::{self, Madt, MadtEntry, MadtIoApic, MadtIntSrcOverride}; + +use crate::arch::interrupt::irq; +use crate::memory::Frame; +use crate::paging::{ActivePageTable, entry::EntryFlags, Page, PhysicalAddress, VirtualAddress}; +use crate::syscall::io::Mmio; + +use super::pic; + +pub struct IoApicRegs { + pointer: *const u32, +} +impl IoApicRegs { + fn ioregsel(&self) -> *const u32 { + self.pointer + } + fn iowin(&self) -> *const u32 { + // offset 0x10 + unsafe { self.pointer.offset(4) } + } + fn read_ioregsel(&self) -> u32 { + unsafe { ptr::read_volatile::(self.ioregsel()) } + } + fn write_ioregsel(&mut self, value: u32) { + unsafe { ptr::write_volatile::(self.ioregsel() as *mut u32, value) } + } + fn read_iowin(&self) -> u32 { + unsafe { ptr::read_volatile::(self.iowin()) } + } + fn write_iowin(&mut self, value: u32) { + unsafe { ptr::write_volatile::(self.iowin() as *mut u32, value) } + } + fn read_reg(&mut self, reg: u8) -> u32 { + self.write_ioregsel(reg.into()); + self.read_iowin() + } + fn write_reg(&mut self, reg: u8, value: u32) { + self.write_ioregsel(reg.into()); + self.write_iowin(value); + } + pub fn read_ioapicid(&mut self) -> u32 { + self.read_reg(0x00) + } + pub fn write_ioapicid(&mut self, value: u32) { + self.write_reg(0x00, value); + } + pub fn read_ioapicver(&mut self) -> u32 { + self.read_reg(0x01) + } + pub fn read_ioapicarb(&mut self) -> u32 { + self.read_reg(0x02) + } + pub fn read_ioredtbl(&mut self, idx: u8) -> u64 { + assert!(idx < 24); + let lo = self.read_reg(0x10 + idx * 2); + let hi = self.read_reg(0x10 + idx * 2 + 1); + + u64::from(lo) | (u64::from(hi) << 32) + } + pub fn write_ioredtbl(&mut self, idx: u8, value: u64) { + assert!(idx < 24); + + let lo = value as u32; + let hi = (value >> 32) as u32; + + self.write_reg(0x10 + idx * 2, lo); + self.write_reg(0x10 + idx * 2 + 1, hi); + } + + pub fn max_redirection_table_entries(&mut self) -> u8 { + let ver = self.read_ioapicver(); + ((ver & 0x00FF_0000) >> 16) as u8 + } + pub fn id(&mut self) -> u8 { + let id_reg = self.read_ioapicid(); + ((id_reg & 0x0F00_0000) >> 24) as u8 + } +} +pub struct IoApic { + regs: Mutex, + gsi_start: u32, + count: u8, +} +impl IoApic { + pub fn new(regs_base: *const u32, gsi_start: u32) -> Self { + let mut regs = IoApicRegs { pointer: regs_base }; + let count = regs.max_redirection_table_entries(); + + Self { + regs: Mutex::new(regs), + gsi_start, + count, + } + } + /// Map an interrupt vector to a physical local APIC ID of a processor (thus physical mode). + pub fn map(&self, idx: u8, info: MapInfo) { + self.regs.lock().write_ioredtbl(idx, info.as_raw()) + } + pub fn set_mask(&self, gsi: u32, mask: bool) { + let idx = (gsi - self.gsi_start) as u8; + let mut guard = self.regs.lock(); + + let mut reg = guard.read_ioredtbl(idx); + reg &= !(1 << 16); + reg |= u64::from(mask) << 16; + guard.write_ioredtbl(idx, reg); + } +} +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum ApicTriggerMode { + Edge = 0, + Level = 1, +} +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum ApicPolarity { + ActiveHigh = 0, + ActiveLow = 1, +} +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum DestinationMode { + Physical = 0, + Logical = 1, +} +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum DeliveryMode { + Fixed = 0b000, + LowestPriority = 0b001, + Smi = 0b010, + Nmi = 0b100, + Init = 0b101, + ExtInt = 0b111, +} + +#[derive(Clone, Copy, Debug)] +pub struct MapInfo { + pub dest: u8, + pub mask: bool, + pub trigger_mode: ApicTriggerMode, + pub polarity: ApicPolarity, + pub dest_mode: DestinationMode, + pub delivery_mode: DeliveryMode, + pub vector: u8, +} + +impl MapInfo { + pub fn as_raw(&self) -> u64 { + assert!(self.vector >= 0x20); + assert!(self.vector <= 0xFE); + + // TODO: Check for reserved fields. + + (u64::from(self.dest) << 56) + | (u64::from(self.mask) << 16) + | ((self.trigger_mode as u64) << 15) + | ((self.polarity as u64) << 13) + | ((self.dest_mode as u64) << 11) + | ((self.delivery_mode as u64) << 8) + | u64::from(self.vector) + } +} + +impl fmt::Debug for IoApic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + struct RedirTable<'a>(&'a Mutex); + + impl<'a> fmt::Debug for RedirTable<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut guard = self.0.lock(); + + let count = guard.max_redirection_table_entries(); + f.debug_list().entries((0..count).map(|i| guard.read_ioredtbl(i))).finish() + } + } + + f.debug_struct("IoApic") + .field("redir_table", &RedirTable(&self.regs)) + .field("gsi_start", &self.gsi_start) + .field("count", &self.count) + .finish() + } +} + +#[derive(Clone, Copy, Debug)] +pub enum TriggerMode { + ConformsToSpecs, + Edge, + Level, +} + +#[derive(Clone, Copy, Debug)] +pub enum Polarity { + ConformsToSpecs, + ActiveHigh, + ActiveLow, +} + +#[derive(Clone, Copy, Debug)] +pub struct Override { + bus_irq: u8, + gsi: u32, + + trigger_mode: TriggerMode, + polarity: Polarity, +} + +// static mut because only the AP initializes the I/O Apic, and when that is done, it's solely +// accessed immutably. +static mut IOAPICS: Option> = None; + +// static mut for the same reason as above +static mut SRC_OVERRIDES: Option> = None; + +pub fn ioapics() -> &'static [IoApic] { + unsafe { + IOAPICS.as_ref().map_or(&[], |vector| &vector[..]) + } +} +pub fn src_overrides() -> &'static [Override] { + unsafe { + SRC_OVERRIDES.as_ref().map_or(&[], |vector| &vector[..]) + } +} + +#[cfg(feature = "acpi")] +pub unsafe fn handle_ioapic(active_table: &mut ActivePageTable, madt_ioapic: &'static MadtIoApic) { + // map the I/O APIC registers + let frame = Frame::containing_address(PhysicalAddress::new(madt_ioapic.address as usize)); + let page = Page::containing_address(VirtualAddress::new(madt_ioapic.address as usize + crate::KERNEL_OFFSET)); + + assert_eq!(active_table.translate_page(page), None); + + let result = active_table.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::GLOBAL | EntryFlags::WRITABLE | EntryFlags::NO_CACHE); + result.flush(active_table); + + let ioapic_registers = page.start_address().get() as *const u32; + let mut ioapic = IoApic::new(ioapic_registers, madt_ioapic.gsi_base); + + assert_eq!(ioapic.regs.lock().id(), madt_ioapic.id, "mismatched ACPI MADT I/O APIC ID, and the ID reported by the I/O APIC"); + + IOAPICS.get_or_insert_with(Vec::new).push(ioapic); +} +#[cfg(feature = "acpi")] +pub unsafe fn handle_src_override(src_override: &'static MadtIntSrcOverride) { + let flags = src_override.flags; + + let polarity_raw = (flags & 0x0003) as u8; + let trigger_mode_raw = ((flags & 0x000C) >> 2) as u8; + + let polarity = match polarity_raw { + 0b00 => Polarity::ConformsToSpecs, + 0b01 => Polarity::ActiveHigh, + 0b10 => return, // reserved + 0b11 => Polarity::ActiveLow, + + _ => unreachable!(), + }; + + let trigger_mode = match trigger_mode_raw { + 0b00 => TriggerMode::ConformsToSpecs, + 0b01 => TriggerMode::Edge, + 0b10 => return, // reserved + 0b11 => TriggerMode::Level, + _ => unreachable!(), + }; + + let over = Override { + bus_irq: src_override.irq_source, + gsi: src_override.gsi_base, + polarity, + trigger_mode, + }; + SRC_OVERRIDES.get_or_insert_with(Vec::new).push(over); +} + +pub unsafe fn init(active_table: &mut ActivePageTable) { + let mut bsp_apic_id = x86::cpuid::CpuId::new().get_feature_info().unwrap().initial_local_apic_id(); // TODO + + // search the madt for all IOAPICs. + #[cfg(feature = "acpi")] + { + let madt: &'static Madt = match madt::MADT.as_ref() { + Some(m) => m, + // TODO: Parse MP tables too. + None => return, + }; + if madt.flags & madt::FLAG_PCAT != 0 { + pic::disable(); + } + + // find all I/O APICs (usually one). + + for entry in madt.iter() { + match entry { + MadtEntry::IoApic(ioapic) => handle_ioapic(active_table, ioapic), + MadtEntry::IntSrcOverride(src_override) => handle_src_override(src_override), + _ => (), + } + } + } + println!("I/O APICs: {:?}, overrides: {:?}", ioapics(), src_overrides()); + + // map the legacy PC-compatible IRQs (0-15) to 32-47, just like we did with 8259 PIC (if it + // wouldn't have been disabled due to this I/O APIC) + for legacy_irq in 0..=15 { + let (gsi, trigger_mode, polarity) = match get_override(legacy_irq) { + Some(over) => (over.gsi, over.trigger_mode, over.polarity), + None => { + if src_overrides().iter().any(|over| over.gsi == u32::from(legacy_irq) && over.bus_irq != legacy_irq) && !src_overrides().iter().any(|over| over.bus_irq == legacy_irq) { + // there's an IRQ conflict, making this legacy IRQ inaccessible. + continue; + } + (legacy_irq.into(), TriggerMode::ConformsToSpecs, Polarity::ConformsToSpecs) + } + }; + let apic = match find_ioapic(gsi) { + Some(ioapic) => ioapic, + None => { + println!("Unable to find a suitable APIC for legacy IRQ {} (GSI {}). It will not be mapped.", legacy_irq, gsi); + continue; + } + }; + let redir_tbl_index = (gsi - apic.gsi_start) as u8; + + let map_info = MapInfo { + // only send to the BSP + dest: bsp_apic_id, + dest_mode: DestinationMode::Physical, + delivery_mode: DeliveryMode::Fixed, + mask: false, + polarity: match polarity { + Polarity::ActiveHigh => ApicPolarity::ActiveHigh, + Polarity::ActiveLow => ApicPolarity::ActiveLow, + Polarity::ConformsToSpecs => ApicPolarity::ActiveHigh, + }, + trigger_mode: match trigger_mode { + TriggerMode::Edge => ApicTriggerMode::Edge, + TriggerMode::Level => ApicTriggerMode::Level, + TriggerMode::ConformsToSpecs => ApicTriggerMode::Edge, + }, + vector: 32 + legacy_irq, + }; + apic.map(redir_tbl_index, map_info); + } + println!("I/O APICs: {:?}, overrides: {:?}", ioapics(), src_overrides()); + irq::set_irq_method(irq::IrqMethod::Apic); + + // tell the firmware that we're using APIC rather than the default 8259 PIC. + #[cfg(feature = "acpi")] + { + let method = { + let namespace_guard = crate::acpi::ACPI_TABLE.namespace.read(); + if let Some(value) = namespace_guard.as_ref().unwrap().get("\\_PIC") { + value.get_as_method().ok() + } else { + None + } + }; + if let Some(m) = method { + m.execute("\\_PIC".into(), vec!(crate::acpi::aml::AmlValue::Integer(1))); + } + } +} +fn get_override(irq: u8) -> Option<&'static Override> { + src_overrides().iter().find(|over| over.bus_irq == irq) +} +fn resolve(irq: u8) -> u32 { + get_override(irq).map_or(u32::from(irq), |over| over.gsi) +} +fn find_ioapic(gsi: u32) -> Option<&'static IoApic> { + ioapics().iter().find(|apic| gsi >= apic.gsi_start && gsi < apic.gsi_start + u32::from(apic.count)) +} + +pub unsafe fn mask(irq: u8) { + let gsi = resolve(irq); + let apic = match find_ioapic(gsi) { + Some(a) => a, + None => return, + }; + apic.set_mask(gsi, true); +} +pub unsafe fn unmask(irq: u8) { + let gsi = resolve(irq); + let apic = match find_ioapic(gsi) { + Some(a) => a, + None => return, + }; + apic.set_mask(gsi, false); +} diff --git a/src/arch/x86_64/device/mod.rs b/src/arch/x86_64/device/mod.rs index 3c87652..0c3f4de 100644 --- a/src/arch/x86_64/device/mod.rs +++ b/src/arch/x86_64/device/mod.rs @@ -1,6 +1,7 @@ use crate::paging::ActivePageTable; pub mod cpu; +pub mod ioapic; pub mod local_apic; pub mod pic; pub mod pit; @@ -9,10 +10,14 @@ pub mod serial; #[cfg(feature = "acpi")] pub mod hpet; -pub unsafe fn init(active_table: &mut ActivePageTable){ +pub unsafe fn init(active_table: &mut ActivePageTable) { pic::init(); local_apic::init(active_table); } +pub unsafe fn init_after_acpi(active_table: &mut ActivePageTable) { + // this will disable the IOAPIC if needed. + //ioapic::init(active_table); +} #[cfg(feature = "acpi")] unsafe fn init_hpet() -> bool { diff --git a/src/arch/x86_64/device/pic.rs b/src/arch/x86_64/device/pic.rs index 88f5709..2518550 100644 --- a/src/arch/x86_64/device/pic.rs +++ b/src/arch/x86_64/device/pic.rs @@ -1,4 +1,5 @@ use crate::syscall::io::{Io, Pio}; +use crate::arch::interrupt::irq; pub static mut MASTER: Pic = Pic::new(0x20); pub static mut SLAVE: Pic = Pic::new(0xA0); @@ -27,6 +28,14 @@ pub unsafe fn init() { // Ack remaining interrupts MASTER.ack(); SLAVE.ack(); + + // probably already set to PIC, but double-check + irq::set_irq_method(irq::IrqMethod::Pic); +} + +pub unsafe fn disable() { + MASTER.data.write(0xFF); + SLAVE.data.write(0xFF); } pub struct Pic { diff --git a/src/arch/x86_64/interrupt/irq.rs b/src/arch/x86_64/interrupt/irq.rs index 72c2972..9b4ba74 100644 --- a/src/arch/x86_64/interrupt/irq.rs +++ b/src/arch/x86_64/interrupt/irq.rs @@ -1,7 +1,7 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use crate::context::timeout; -use crate::device::{local_apic, pic}; +use crate::device::{local_apic, ioapic, pic}; use crate::device::serial::{COM1, COM2}; use crate::ipi::{ipi, IpiKind, IpiTarget}; use crate::scheme::debug::debug_input; @@ -11,38 +11,99 @@ use crate::{context, ptrace, time}; #[thread_local] pub static PIT_TICKS: AtomicUsize = AtomicUsize::new(0); +#[repr(u8)] +pub enum IrqMethod { + Pic = 0, + Apic = 1, +} + +static IRQ_METHOD: AtomicUsize = AtomicUsize::new(IrqMethod::Pic as usize); + +pub fn set_irq_method(method: IrqMethod) { + IRQ_METHOD.store(method as usize, core::sync::atomic::Ordering::Release); +} + +fn irq_method() -> IrqMethod { + let raw = IRQ_METHOD.load(core::sync::atomic::Ordering::Acquire); + + match raw { + 0 => IrqMethod::Pic, + 1 => IrqMethod::Apic, + _ => unreachable!(), + } +} + extern { + // triggers irq scheme fn irq_trigger(irq: u8); } +/// Notify the IRQ scheme that an IRQ has been registered. This should mask the IRQ until the +/// scheme user unmasks it ("acknowledges" it). unsafe fn trigger(irq: u8) { - - if irq < 16 { - if irq >= 8 { - pic::SLAVE.mask_set(irq - 8); - pic::MASTER.ack(); - pic::SLAVE.ack(); - } else { - pic::MASTER.mask_set(irq); - pic::MASTER.ack(); - } + match irq_method() { + IrqMethod::Pic => if irq < 16 { pic_mask(irq) }, + IrqMethod::Apic => ioapic_mask(irq), } - irq_trigger(irq); } +/// Unmask the IRQ. This is called from the IRQ scheme, which does this when a user process has +/// processed the IRQ. +pub unsafe fn acknowledge(irq: usize) { + match irq_method() { + IrqMethod::Pic => if irq < 16 { pic_unmask(irq) }, + IrqMethod::Apic => ioapic_unmask(irq), + } +} +/// Sends an end-of-interrupt, so that the interrupt controller can go on to the next one. +pub unsafe fn eoi(irq: u8) { + match irq_method() { + IrqMethod::Pic => if irq < 16 { pic_eoi(irq) }, + IrqMethod::Apic => lapic_eoi(), + } +} + +unsafe fn pic_mask(irq: u8) { + debug_assert!(irq < 16); + + if irq >= 8 { + pic::SLAVE.mask_set(irq - 8); + } else { + pic::MASTER.mask_set(irq); + } +} + +unsafe fn ioapic_mask(irq: u8) { + ioapic::mask(irq); +} + + +unsafe fn pic_eoi(irq: u8) { + debug_assert!(irq < 16); + + if irq >= 8 { + pic::MASTER.ack(); + pic::SLAVE.ack(); + } else { + pic::MASTER.ack(); + } +} unsafe fn lapic_eoi() { local_apic::LOCAL_APIC.eoi() } -pub unsafe fn acknowledge(irq: usize) { - if irq < 16 { - if irq >= 8 { - pic::SLAVE.mask_clear(irq as u8 - 8); - } else { - pic::MASTER.mask_clear(irq as u8); - } +unsafe fn pic_unmask(irq: usize) { + debug_assert!(irq < 16); + + if irq >= 8 { + pic::SLAVE.mask_clear(irq as u8 - 8); + } else { + pic::MASTER.mask_clear(irq as u8); } } +unsafe fn ioapic_unmask(irq: usize) { + ioapic::unmask(irq as u8); +} interrupt_stack!(pit, stack, { // Saves CPU time by not sending IRQ event irq_trigger(0); @@ -56,7 +117,7 @@ interrupt_stack!(pit, stack, { offset.0 += sum / 1_000_000_000; } - pic::MASTER.ack(); + eoi(0); // Wake up other CPUs ipi(IpiKind::Pit, IpiTarget::Other); @@ -72,68 +133,80 @@ interrupt_stack!(pit, stack, { interrupt!(keyboard, { trigger(1); + eoi(1); }); interrupt!(cascade, { // No need to do any operations on cascade - pic::MASTER.ack(); + eoi(2); }); interrupt!(com2, { while let Some(c) = COM2.lock().receive() { debug_input(c); } - pic::MASTER.ack(); + eoi(3); }); interrupt!(com1, { while let Some(c) = COM1.lock().receive() { debug_input(c); } - pic::MASTER.ack(); + eoi(4); }); interrupt!(lpt2, { + eoi(5); trigger(5); }); interrupt!(floppy, { + eoi(6); trigger(6); }); interrupt!(lpt1, { + eoi(7); trigger(7); }); interrupt!(rtc, { + eoi(8); trigger(8); }); interrupt!(pci1, { + eoi(9); trigger(9); }); interrupt!(pci2, { + eoi(10); trigger(10); }); interrupt!(pci3, { + eoi(11); trigger(11); }); interrupt!(mouse, { + eoi(12); trigger(12); }); interrupt!(fpu, { + eoi(13); trigger(13); }); interrupt!(ata1, { + eoi(14); trigger(14); }); interrupt!(ata2, { + eoi(15); trigger(15); }); interrupt!(lapic_timer, { @@ -154,7 +227,7 @@ interrupt!(calib_pit, { offset.0 += sum / 1_000_000_000; } - pic::MASTER.ack(); + eoi(0); }); // XXX: This would look way prettier using const generics. diff --git a/src/arch/x86_64/paging/mod.rs b/src/arch/x86_64/paging/mod.rs index c4acb93..f748d97 100644 --- a/src/arch/x86_64/paging/mod.rs +++ b/src/arch/x86_64/paging/mod.rs @@ -125,6 +125,7 @@ pub unsafe fn init( kernel_end: usize, stack_start: usize, stack_end: usize, + other_ro_ranges: &[(usize, usize)], // base + length ) -> (ActivePageTable, usize) { extern "C" { /// The starting byte of the text (code) data segment. @@ -266,6 +267,21 @@ pub unsafe fn init( result.ignore(); } } + // Map all other necessary address ranges coming from the bootloader. + // The address ranges may overlap, but this is not a problem since they have the same + // flags. + { + for (range_start, range_size) in other_ro_ranges { + let start_phys_addr = Frame::containing_address(PhysicalAddress::new((range_start / 4096) * 4096 - crate::KERNEL_OFFSET)); + let end_phys_addr = Frame::containing_address(PhysicalAddress::new(((range_start + range_size + 4095) / 4096) * 4096 - crate::KERNEL_OFFSET)); + + for frame in Frame::range_inclusive(start_phys_addr, end_phys_addr) { + let page = Page::containing_address(VirtualAddress::new(crate::KERNEL_OFFSET + frame.start_address().get())); + let result = mapper.map_to(page, frame, EntryFlags::PRESENT | EntryFlags::GLOBAL | EntryFlags::NO_EXECUTE); + result.ignore(); + } + } + } }); // This switches the active table, which is setup by the bootloader, to a correct table diff --git a/src/arch/x86_64/start.rs b/src/arch/x86_64/start.rs index 226c9a2..11f8404 100644 --- a/src/arch/x86_64/start.rs +++ b/src/arch/x86_64/start.rs @@ -3,7 +3,7 @@ /// It must create the IDT with the correct entries, those entries are /// defined in other files inside of the `arch` module -use core::slice; +use core::{mem, slice}; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use crate::allocator; @@ -45,6 +45,17 @@ pub struct KernelArgs { stack_size: u64, env_base: u64, env_size: u64, + + /// The base 64-bit pointer to an array of saved RSDPs. It's up to the kernel (and possibly + /// userspace), to decide which RSDP to use. The buffer will be a linked list containing a + /// 32-bit relative (to this field) next, and the actual struct afterwards. + /// + /// This field can be NULL, and if so, the system has not booted with UEFI or in some other way + /// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS + /// memory instead. On UEFI systems, searching is not guaranteed to actually work though. + acpi_rsdps_base: u64, + /// The size of the RSDPs region. + acpi_rsdps_size: u64, } /// The entry to Rust, all things must be initialized @@ -59,6 +70,8 @@ pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! { let stack_size = args.stack_size as usize; let env_base = args.env_base as usize; let env_size = args.env_size as usize; + let acpi_rsdps_base = args.acpi_rsdps_base; + let acpi_rsdps_size = args.acpi_rsdps_size; // BSS should already be zero { @@ -72,6 +85,13 @@ pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! { println!("Kernel: {:X}:{:X}", kernel_base, kernel_base + kernel_size); println!("Stack: {:X}:{:X}", stack_base, stack_base + stack_size); println!("Env: {:X}:{:X}", env_base, env_base + env_size); + println!("RSDPs: {:X}:{:X}", acpi_rsdps_base, acpi_rsdps_base + acpi_rsdps_size); + + let ext_mem_ranges = if args.acpi_rsdps_base != 0 && args.acpi_rsdps_size > 0 { + Some([(acpi_rsdps_base as usize, acpi_rsdps_size as usize)]) + } else { + None + }; // Set up GDT before paging gdt::init(); @@ -83,7 +103,7 @@ pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! { memory::init(0, kernel_base + ((kernel_size + 4095)/4096) * 4096); // Initialize paging - let (mut active_table, tcb_offset) = paging::init(0, kernel_base, kernel_base + kernel_size, stack_base, stack_base + stack_size); + let (mut active_table, tcb_offset) = paging::init(0, kernel_base, kernel_base + kernel_size, stack_base, stack_base + stack_size, ext_mem_ranges.as_ref().map(|arr| &arr[..]).unwrap_or(&[])); // Set up GDT after paging with TLS gdt::init_paging(tcb_offset, stack_base + stack_size); @@ -124,7 +144,10 @@ pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! { // Read ACPI tables, starts APs #[cfg(feature = "acpi")] - acpi::init(&mut active_table); + { + acpi::init(&mut active_table, if acpi_rsdps_base != 0 && acpi_rsdps_size > 0 { Some((acpi_rsdps_base, acpi_rsdps_size)) } else { None }); + device::init_after_acpi(&mut active_table); + } // Initialize all of the non-core devices not otherwise needed to complete initialization device::init_noncore(); diff --git a/src/scheme/acpi.rs b/src/scheme/acpi.rs new file mode 100644 index 0000000..12eb2d7 --- /dev/null +++ b/src/scheme/acpi.rs @@ -0,0 +1,454 @@ +use core::convert::{TryFrom, TryInto}; +use core::fmt::Write; +use core::str; +use core::sync::atomic::{self, AtomicUsize}; + +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +use syscall::data::Stat; +use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; +use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDWR, O_STAT, O_WRONLY}; +use syscall::scheme::Scheme; +use syscall::{Error, Result}; +use syscall::{MODE_DIR, MODE_FILE, SEEK_CUR, SEEK_END, SEEK_SET}; + +use spin::{Mutex, RwLock}; + +use crate::acpi::sdt::Sdt; +use crate::acpi::SdtSignature; +use crate::paging::ActivePageTable; + +#[derive(Clone, Copy)] +struct PhysSlice { + phys_ptr: usize, + len: usize, + /// These appear to be identity mapped, so this is technically not needed. + virt: usize, +} + +/// A scheme used to access ACPI tables needed for some drivers to function (e.g. pcid with the +/// PCIe "MCFG" table). +/// +/// # Layout +/// * `/tables` +/// * _can be listed to retrieve the available tables_ +/// * e.g. MCFG-- +/// * _maybe_ the MADT, in case some userspace driver takes care of the I/O APIC. +/// * _perhaps_ some interface for e.g. power management. +pub struct AcpiScheme { + handles: RwLock>>, + tables: Vec<(SdtSignature, PhysSlice)>, + next_fd: AtomicUsize, +} + +const TOPLEVEL_DIR_CONTENTS: &[u8] = b"tables\n"; +const ALLOWED_TABLES: &[[u8; 4]] = &[*b"MCFG", *b"APIC"]; + +// XXX: Why can't core also have something like std::io::Take? It's not even real I/O! +/// An internal wrapper struct that limits the number of bytes that can be fmt-written, in order to +/// properly return the length when reading directories etc. The bytes that cannot be written will +/// be discarded. +struct Take<'a> { + buf: &'a mut [u8], + offset: usize, +} + +impl Take<'_> { + pub fn write_to_buf<'a>(buf: &'a mut [u8]) -> Take<'a> { + Take { offset: 0, buf } + } + pub fn bytes_currently_written(&self) -> usize { + self.offset + } +} + +impl<'a> core::fmt::Write for Take<'a> { + fn write_str(&mut self, string: &str) -> core::fmt::Result { + if self.offset > self.buf.len() { + return Ok(()); + } + + let string_bytes = string.as_bytes(); + let max = core::cmp::min(string_bytes.len() + self.offset, self.buf.len()) - self.offset; + self.buf[self.offset..self.offset + max].copy_from_slice(&string_bytes[..max]); + self.offset += max; + Ok(()) + } +} + +enum Handle { + TopLevel(usize), // seek offset + Tables(usize), // seek offset + + Table { + name: [u8; 4], + oem_id: [u8; 6], + oem_table_id: [u8; 8], + + offset: usize, // seek offset + }, +} + +impl AcpiScheme { + fn get_tables() -> Vec<(SdtSignature, PhysSlice)> { + let mut active_table = unsafe { ActivePageTable::new() }; + + let mut tables = Vec::new(); + + for allowed_tbl_name in ALLOWED_TABLES.iter() { + use crate::acpi::{find_sdt, get_sdt, get_sdt_signature}; + + // it appears that the SDTs are identity mapped, in which case we can just call get_sdt + // whenever we need to and use the slice as if it was physical. + + let table_name_str = + str::from_utf8(allowed_tbl_name).expect("ACPI table name wasn't correct UTF-8"); + + for sdt in find_sdt(table_name_str) { + let virt = + get_sdt(sdt as *const Sdt as usize, &mut active_table) as *const Sdt as usize; + let signature = get_sdt_signature(sdt); + let sdt_pointer = sdt as *const Sdt as usize; + let len = sdt.length as usize; + assert_eq!(virt, sdt_pointer); + tables.push(( + signature, + PhysSlice { + phys_ptr: sdt_pointer, + len, + virt, + }, + )); + } + } + tables + } + pub fn new() -> Self { + Self { + handles: RwLock::new(BTreeMap::new()), + tables: Self::get_tables(), + next_fd: AtomicUsize::new(0), + } + } + fn lookup_signature_index( + &self, + name: [u8; 4], + oem_id: [u8; 6], + oem_table_id: [u8; 8], + ) -> Option { + self.tables + .iter() + .position(|((sig_name, sig_oem_id, sig_oem_table_id), _)| { + sig_name.as_bytes() == &name + && sig_oem_id == &oem_id + && sig_oem_table_id == &oem_table_id + }) + } + fn lookup_signature( + &self, + name: [u8; 4], + oem_id: [u8; 6], + oem_table_id: [u8; 8], + ) -> Option { + Some(self.tables[self.lookup_signature_index(name, oem_id, oem_table_id)?].1) + } +} + +fn parse_table_filename(filename: &[u8]) -> Option<([u8; 4], [u8; 6], [u8; 8])> { + // the table identifier takes the form: + // 1. a four byte table name, like 'APIC' (MADT) or 'MCFG'. + // 2. a dash followed by 12 hexadecimal digits (6 bytes when decoded) composing the OEM ID. + // 3. another dash followed by 16 hex digits (8 bytes), composing the OEM Table ID. + // hence, the table is 4 + 1 + 12 + 1 + 16 = 34 bytes long. + if filename.len() != 34 { + return None; + } + let mut table_identifier = [0u8; 34]; + table_identifier.copy_from_slice(filename); + + let table_name = &table_identifier[..4]; + if table_identifier[4] != b'-' { + return None; + } + let oem_id_hex = &table_identifier[5..17]; + if table_identifier[17] != b'-' { + return None; + } + let oem_table_id_hex = &table_identifier[18..34]; + + let oem_id_hex_str = str::from_utf8(oem_id_hex).ok()?; + let oem_table_id_hex_str = str::from_utf8(oem_table_id_hex).ok()?; + + let mut oem_id = [0u8; 6]; + + for index in 0..oem_id.len() { + oem_id[index] = u8::from_str_radix(&oem_id_hex_str[index * 2..(index + 1) * 2], 16).ok()?; + } + + let mut oem_table_id = [0u8; 8]; + + for index in 0..oem_table_id.len() { + oem_table_id[index] = + u8::from_str_radix(&oem_table_id_hex_str[index * 2..(index + 1) * 2], 16).ok()?; + } + + Some((table_name.try_into().unwrap(), oem_id, oem_table_id)) +} +fn serialize_table_filename( + buffer: &mut [u8], + (table_name, oem_id, oem_table_id): ([u8; 4], [u8; 6], [u8; 8]), +) -> usize { + let mut wrapper = Take::write_to_buf(buffer); + write!( + wrapper, + "{}-", + str::from_utf8(&table_name).expect("Acpi table id wasn't valid UTF-8") + ) + .unwrap(); + for b in &oem_id { + write!(wrapper, "{:2x}", b).unwrap(); + } + write!(wrapper, "-").unwrap(); + for b in &oem_table_id { + write!(wrapper, "{:2x}", b).unwrap(); + } + wrapper.bytes_currently_written() +} + +impl Scheme for AcpiScheme { + fn open(&self, path: &[u8], flags: usize, opener_uid: u32, _opener_gid: u32) -> Result { + if opener_uid != 0 { + return Err(Error::new(EACCES)); + } + + let path_str = str::from_utf8(path).or(Err(Error::new(ENOENT)))?; + let path_str = path_str.trim_start_matches('/'); + + // TODO: Use some kind of component iterator. + + let new_handle = if path_str.starts_with("tables") { + let subpath = (&path_str[6..]).trim_start_matches('/'); + + if subpath.is_empty() { + // List of ACPI tables + if (flags & O_DIRECTORY == 0 && flags & O_STAT == 0) + || (flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR) + { + return Err(Error::new(EISDIR)); + } + Handle::Tables(0) + } else { + if (flags & O_DIRECTORY != 0 && flags & O_STAT == 0) { + return Err(Error::new(ENOTDIR)); + } + if flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR { + return Err(Error::new(EINVAL)); + } + let (name, oem_id, oem_table_id) = + parse_table_filename(subpath.as_bytes()).ok_or(Error::new(ENOENT))?; + + if self + .lookup_signature_index(name, oem_id, oem_table_id) + .is_none() + { + return Err(Error::new(ENOENT)); + } + Handle::Table { + name, + oem_id, + oem_table_id, + offset: 0, + } + } + } else if path.is_empty() { + // Top-level + if (flags & O_DIRECTORY == 0 && flags & O_STAT == 0) + || (flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR) + { + return Err(Error::new(EISDIR)); + } + Handle::TopLevel(0) + } else { + return Err(Error::new(ENOENT)); + }; + let new_fd = self.next_fd.fetch_add(1, atomic::Ordering::SeqCst); + self.handles.write().insert(new_fd, Mutex::new(new_handle)); + Ok(new_fd) + } + fn fpath(&self, id: usize, buf: &mut [u8]) -> Result { + let handles_guard = self.handles.read(); + let handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + Ok(match &*handle { + &Handle::TopLevel(_) => { + let path = b"acpi:"; + let max = core::cmp::min(buf.len(), path.len()); + buf[..max].copy_from_slice(&path[..]); + max + } + &Handle::Tables(_) => { + let path = b"acpi:tables"; + let max = core::cmp::min(buf.len(), path.len()); + buf[..max].copy_from_slice(&path[..]); + max + } + &Handle::Table { + name, + oem_id, + oem_table_id, + .. + } => { + let base_path = b"acpi:tables/"; + let base_max = core::cmp::min(buf.len(), base_path.len()); + buf[..base_max].copy_from_slice(&base_path[..]); + serialize_table_filename(&mut buf[base_max..], (name, oem_id, oem_table_id)) + } + }) + } + fn fstat(&self, id: usize, stat: &mut Stat) -> Result { + let handles_guard = self.handles.read(); + let handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + match &*handle { + &Handle::TopLevel(_) => { + stat.st_mode = MODE_DIR; + stat.st_size = TOPLEVEL_DIR_CONTENTS.len() as u64; + } + &Handle::Tables(_) => { + stat.st_mode = MODE_DIR; + stat.st_size = (self.tables.len() * 35) as u64; // fixed size of 34 bytes for the file names, plus a newline + } + &Handle::Table { + name, + oem_id, + oem_table_id, + .. + } => { + let len = self + .lookup_signature(name, oem_id, oem_table_id) + .ok_or(Error::new(EBADFD))? + .len; + + stat.st_mode = MODE_FILE; + stat.st_size = len as u64; + } + } + Ok(0) + } + fn seek(&self, id: usize, pos: usize, whence: usize) -> Result { + let handles_guard = self.handles.read(); + let mut handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + let (cur_offset, length) = match &*handle { + &Handle::TopLevel(offset) => (offset, TOPLEVEL_DIR_CONTENTS.len()), + &Handle::Tables(offset) => (offset, self.tables.len() * 35), + &Handle::Table { + name, + oem_id, + oem_table_id, + offset, + } => ( + offset, + self.lookup_signature(name, oem_id, oem_table_id) + .ok_or(Error::new(EBADFD))? + .len, + ), + }; + let new_offset = match whence { + SEEK_CUR => core::cmp::min(cur_offset + pos, length), + SEEK_END => core::cmp::min(length + pos, length), + SEEK_SET => core::cmp::min(length, pos), + _ => return Err(Error::new(EINVAL)), + }; + match &mut *handle { + &mut Handle::Table { ref mut offset, .. } + | &mut Handle::Tables(ref mut offset) + | &mut Handle::TopLevel(ref mut offset) => *offset = new_offset, + } + Ok(new_offset) + } + fn read(&self, id: usize, mut buf: &mut [u8]) -> Result { + let handles_guard = self.handles.read(); + let mut handle = handles_guard.get(&id).ok_or(Error::new(EBADF))?.lock(); + + match &mut *handle { + &mut Handle::TopLevel(ref mut offset) => { + let max_bytes_to_read = core::cmp::min(buf.len(), TOPLEVEL_DIR_CONTENTS.len()); + let bytes_to_read = core::cmp::max(max_bytes_to_read, *offset) - *offset; + buf[..bytes_to_read] + .copy_from_slice(&TOPLEVEL_DIR_CONTENTS[*offset..*offset + bytes_to_read]); + *offset += bytes_to_read; + Ok(bytes_to_read) + } + &mut Handle::Tables(ref mut offset) => { + if *offset >= self.tables.len() * 35 { + return Ok(0); + } + // one really good thing with fixed size filenames, is that no index has to be + // stored anywhere! + let base_table_index = *offset / 35; + let mut bytes_to_skip = *offset % 35; + let mut bytes_read = 0; + + for index in base_table_index..self.tables.len() { + let &(ref name_string, oem_id, oem_table_id) = &self.tables[index].0; + let signature = ( + name_string.as_bytes().try_into().or(Err(Error::new(EIO)))?, + oem_id, + oem_table_id, + ); + + let mut src_buf = [0u8; 35]; + serialize_table_filename(&mut src_buf[..34], signature); + src_buf[34] = b'\n'; + + let max_bytes_to_read = core::cmp::min(buf.len(), src_buf.len()); + let bytes_to_read = + core::cmp::max(max_bytes_to_read, bytes_to_skip) - bytes_to_skip; + buf[..bytes_to_read].copy_from_slice(&src_buf[..bytes_to_read]); + bytes_read += bytes_to_read; + bytes_to_skip = 0; + buf = &mut buf[bytes_to_read..]; + } + *offset += bytes_read; + Ok(bytes_read) + } + &mut Handle::Table { + name, + oem_id, + oem_table_id, + ref mut offset, + } => { + let index = self + .lookup_signature_index(name, oem_id, oem_table_id) + .ok_or(Error::new(EBADFD))?; + let ( + _, + PhysSlice { + phys_ptr, + len, + virt: old_virt, + }, + ) = self.tables[index]; + assert_eq!(phys_ptr, old_virt); + let new_virt = + crate::acpi::get_sdt(phys_ptr, unsafe { &mut ActivePageTable::new() }) + as *const Sdt as usize; + + let table_contents = + unsafe { core::slice::from_raw_parts(new_virt as *const u8, len) }; + + let max_bytes_to_read = core::cmp::min(buf.len(), table_contents.len()); + let bytes_to_read = core::cmp::max(max_bytes_to_read, *offset) - *offset; + buf[..bytes_to_read] + .copy_from_slice(&table_contents[*offset..*offset + bytes_to_read]); + *offset += bytes_to_read; + Ok(bytes_to_read) + } + } + } + fn write(&self, id: usize, buf: &[u8]) -> Result { + Err(Error::new(EBADF)) + } +} diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs index e539de9..5f92107 100644 --- a/src/scheme/mod.rs +++ b/src/scheme/mod.rs @@ -16,6 +16,9 @@ use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::syscall::error::*; use crate::syscall::scheme::Scheme; +#[cfg(feature = "acpi")] +use self::acpi::AcpiScheme; + use self::debug::DebugScheme; use self::event::EventScheme; use self::initfs::InitFsScheme; @@ -28,6 +31,10 @@ use self::root::RootScheme; use self::sys::SysScheme; use self::time::TimeScheme; +/// When compiled with the "acpi" feature - `acpi:` - allows drivers to read a limited set of ACPI tables. +#[cfg(feature = "acpi")] +pub mod acpi; + /// `debug:` - provides access to serial console pub mod debug; @@ -136,6 +143,9 @@ impl SchemeList { let ns = self.new_ns(); // These schemes should only be available on the root + #[cfg(feature = "acpi")] { + self.insert(ns, Box::new(*b"acpi"), |_| Arc::new(AcpiScheme::new())).unwrap(); + } self.insert(ns, Box::new(*b"debug"), |scheme_id| Arc::new(DebugScheme::new(scheme_id))).unwrap(); self.insert(ns, Box::new(*b"initfs"), |_| Arc::new(InitFsScheme::new())).unwrap(); self.insert(ns, Box::new(*b"irq"), |scheme_id| Arc::new(IrqScheme::new(scheme_id))).unwrap();