Merge branch 'ioapic' into 'master'
Support the I/O APIC alongside the 8259 PIC See merge request redox-os/kernel!117
This commit is contained in:
@@ -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<AmlValue>, Box<AmlValue>)
|
||||
}
|
||||
|
||||
#[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<u8>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BufferField {
|
||||
pub source_buf: Box<AmlValue>,
|
||||
pub index: Box<AmlValue>,
|
||||
pub length: Box<AmlValue>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FieldUnit {
|
||||
pub selector: FieldSelector,
|
||||
pub connection: Box<AmlValue>,
|
||||
@@ -60,19 +60,19 @@ pub struct FieldUnit {
|
||||
pub length: usize
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Device {
|
||||
pub obj_list: Vec<String>,
|
||||
pub notify_methods: BTreeMap<u8, Vec<fn()>>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ThermalZone {
|
||||
pub obj_list: Vec<String>,
|
||||
pub notify_methods: BTreeMap<u8, Vec<fn()>>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Processor {
|
||||
pub proc_id: u8,
|
||||
pub p_blk: Option<u32>,
|
||||
@@ -80,7 +80,7 @@ pub struct Processor {
|
||||
pub notify_methods: BTreeMap<u8, Vec<fn()>>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OperationRegion {
|
||||
pub region: RegionSpace,
|
||||
pub offset: Box<AmlValue>,
|
||||
@@ -89,13 +89,14 @@ pub struct OperationRegion {
|
||||
pub accessed_by: Option<u64>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PowerResource {
|
||||
pub system_level: u8,
|
||||
pub resource_order: u16,
|
||||
pub obj_list: Vec<String>
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
||||
@@ -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<Madt> = 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 };
|
||||
|
||||
@@ -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<Option<BTreeMap<SdtSignature, &'static Sdt>>> = RwLock::new(None);
|
||||
pub static SDT_ORDER: RwLock<Option<Vec<SdtSignature>>> = RwLock::new(None);
|
||||
|
||||
|
||||
@@ -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<RSDP> {
|
||||
// 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<Self::Item> {
|
||||
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::<RSDP>() && 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<RSDP> {
|
||||
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<RSDP> {
|
||||
pub fn get_rsdp_by_searching(active_table: &mut ActivePageTable) -> Option<RSDP> {
|
||||
let start_addr = 0xE_0000;
|
||||
let end_addr = 0xF_FFFF;
|
||||
|
||||
|
||||
398
src/arch/x86_64/device/ioapic.rs
Normal file
398
src/arch/x86_64/device/ioapic.rs
Normal file
@@ -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::<u32>(self.ioregsel()) }
|
||||
}
|
||||
fn write_ioregsel(&mut self, value: u32) {
|
||||
unsafe { ptr::write_volatile::<u32>(self.ioregsel() as *mut u32, value) }
|
||||
}
|
||||
fn read_iowin(&self) -> u32 {
|
||||
unsafe { ptr::read_volatile::<u32>(self.iowin()) }
|
||||
}
|
||||
fn write_iowin(&mut self, value: u32) {
|
||||
unsafe { ptr::write_volatile::<u32>(self.iowin() 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<IoApicRegs>,
|
||||
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<IoApicRegs>);
|
||||
|
||||
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<Vec<IoApic>> = None;
|
||||
|
||||
// static mut for the same reason as above
|
||||
static mut SRC_OVERRIDES: Option<Vec<Override>> = 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);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
454
src/scheme/acpi.rs
Normal file
454
src/scheme/acpi.rs
Normal file
@@ -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-<OEM ID in hex>-<OEM TABLE ID in hex>
|
||||
/// * _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<BTreeMap<usize, Mutex<Handle>>>,
|
||||
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<usize> {
|
||||
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<PhysSlice> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user