diff options
-rw-r--r-- | devices/src/ioapic.rs | 167 | ||||
-rw-r--r-- | devices/src/lib.rs | 2 | ||||
-rw-r--r-- | kvm/src/lib.rs | 6 |
3 files changed, 173 insertions, 2 deletions
diff --git a/devices/src/ioapic.rs b/devices/src/ioapic.rs new file mode 100644 index 0000000..a4bb98e --- /dev/null +++ b/devices/src/ioapic.rs @@ -0,0 +1,167 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller +// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification. + +use bit_field::*; +use BusDevice; + +// TODO(mutexlox): once https://crrev.com/c/1519686 has landed, refactor these bitfields to use +// better types where applicable. +#[bitfield] +#[derive(Clone, Copy, PartialEq)] +pub struct RedirectionTableEntry { + vector: BitField8, + delivery_mode: BitField3, + dest_mode: BitField1, + delivery_status: BitField1, + polarity: BitField1, + remote_irr: BitField1, + trigger_mode: BitField1, + interrupt_mask: BitField1, + reserved: BitField39, + dest_id: BitField8, +} + +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq)] +pub enum DeliveryStatus { + Idle = 0, + Pending = 1, +} + +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq)] +pub enum InterruptRemappingFormat { + Compatibility = 0, + Remappable = 1, +} + +#[allow(dead_code)] +const IOAPIC_VERSION_ID: u32 = 0x00170011; +#[allow(dead_code)] +const IOAPIC_BASE_ADDRESS: u32 = 0xfec00000; +// The Intel manual does not specify this size, but KVM uses it. +#[allow(dead_code)] +const IOAPIC_MEM_LENGTH_BYTES: usize = 0x100; + +// Constants for IOAPIC direct register offset. +#[allow(dead_code)] +const IOAPIC_REG_ID: u32 = 0x00; +#[allow(dead_code)] +const IOAPIC_REG_VERSION: u32 = 0x01; +#[allow(dead_code)] +const IOAPIC_REG_ARBITRATION_ID: u32 = 0x02; + +// Register offsets +pub const IOREGSEL_OFF: u64 = 0x0; +pub const IOREGSEL_DUMMY_UPPER_32_BITS_OFF: u64 = 0x4; +pub const IOWIN_OFF: u64 = 0x10; + +// The RTC needs special treatment to work properly for Windows (or other OSs that use tick +// stuffing). In order to avoid time drift, we need to guarantee that the correct number of RTC +// interrupts are injected into the guest. This hack essentialy treats RTC interrupts as level +// triggered, which allows the IOAPIC to be responsible for interrupt coalescing and allows the +// IOAPIC to pass back whether or not the interrupt was coalesced to the CMOS (which allows the +// CMOS to perform tick stuffing). This deviates from the IOAPIC spec in ways very similar to (but +// not exactly the same as) KVM's IOAPIC. +#[allow(dead_code)] +const RTC_IRQ: u32 = 0x8; + +#[allow(dead_code)] +pub struct Ioapic { + id: usize, + // Remote IRR for Edge Triggered Real Time Clock interrupts, which allows the CMOS to know when + // one of its interrupts is being coalesced. + rtc_remote_irr: bool, + current_interrupt_level_bitmap: u32, + redirect_table: [RedirectionTableEntry; kvm::NUM_IOAPIC_PINS], + ioregsel: u8, +} + +impl BusDevice for Ioapic { + fn debug_label(&self) -> String { + "userspace IOAPIC".to_string() + } + + fn read(&mut self, offset: u64, data: &mut [u8]) { + if data.len() > 8 || data.len() == 0 { + warn!("IOAPIC: Bad read size: {}", data.len()); + return; + } + let out = match offset { + IOREGSEL_OFF => self.ioregsel.into(), + IOREGSEL_DUMMY_UPPER_32_BITS_OFF => 0, + IOWIN_OFF => self.ioapic_read(), + _ => { + warn!("IOAPIC: Bad read from offset {}", offset); + return; + } + }; + let out_arr = out.to_ne_bytes(); + for i in 0..4 { + if i < data.len() { + data[i] = out_arr[i]; + } + } + } + + fn write(&mut self, offset: u64, data: &[u8]) { + if data.len() > 8 || data.len() == 0 { + warn!("IOAPIC: Bad write size: {}", data.len()); + return; + } + match offset { + IOREGSEL_OFF => self.ioregsel = data[0], + IOREGSEL_DUMMY_UPPER_32_BITS_OFF => {} // Ignored. + IOWIN_OFF => { + if data.len() != 4 { + warn!("IOAPIC: Bad write size for iowin: {}", data.len()); + return; + } + let data_arr = [data[0], data[1], data[2], data[3]]; + let val = u32::from_ne_bytes(data_arr); + self.ioapic_write(val); + } + _ => { + warn!("IOAPIC: Bad write to offset {}", offset); + return; + } + } + } +} + +impl Ioapic { + pub fn new() -> Ioapic { + let mut entry = RedirectionTableEntry::new(); + entry.set_interrupt_mask(1); + let entries = [entry; kvm::NUM_IOAPIC_PINS]; + Ioapic { + id: 0, + rtc_remote_irr: false, + current_interrupt_level_bitmap: 0, + redirect_table: entries, + ioregsel: 0, + } + } + + // The ioapic must be informed about EOIs in order to avoid sending multiple interrupts of the + // same type at the same time. + pub fn end_of_interrupt(&mut self, _vector: u8) { + unimplemented!(); + } + + pub fn service_irq(&mut self, _irq: u32, _level: bool) -> bool { + unimplemented!(); + } + + fn ioapic_write(&mut self, _val: u32) { + unimplemented!(); + } + + fn ioapic_read(&mut self) -> u32 { + unimplemented!(); + } +} diff --git a/devices/src/lib.rs b/devices/src/lib.rs index d4166eb..442939e 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -34,6 +34,7 @@ mod register_space; mod bus; mod cmos; mod i8042; +mod ioapic; mod pci; mod pit; pub mod pl030; @@ -49,6 +50,7 @@ pub use self::bus::Error as BusError; pub use self::bus::{Bus, BusDevice, BusRange}; pub use self::cmos::Cmos; pub use self::i8042::I8042Device; +pub use self::ioapic::Ioapic; pub use self::pci::{ Ac97Dev, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, }; diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs index 6aa1532..3112fa0 100644 --- a/kvm/src/lib.rs +++ b/kvm/src/lib.rs @@ -274,6 +274,9 @@ pub enum PicId { Secondary = 1, } +/// Number of pins on the IOAPIC. +pub const NUM_IOAPIC_PINS: usize = 24; + // Used to invert the order when stored in a max-heap. #[derive(Copy, Clone, Eq, PartialEq)] struct MemSlot(u32); @@ -937,8 +940,7 @@ impl Vm { pub fn enable_split_irqchip(&self) -> Result<()> { let mut cap: kvm_enable_cap = Default::default(); cap.cap = KVM_CAP_SPLIT_IRQCHIP; - // TODO(mutexlox): When the IOAPIC is implemented, refer to its "number of pins" constant. - cap.args[0] = 24; + cap.args[0] = NUM_IOAPIC_PINS as u64; self.kvm_enable_cap(&cap) } |