diff --git a/Cargo.lock b/Cargo.lock index 0e12198ed3..4f647bca32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1929,6 +1929,16 @@ dependencies = [ "task", ] +[[package]] +name = "lspci" +version = "0.1.0" +dependencies = [ + "app_io", + "getopts", + "memory", + "pci", +] + [[package]] name = "lz4_flex" version = "0.9.3" @@ -2599,6 +2609,7 @@ dependencies = [ "arm_boards", "bit_field 0.7.0", "cpu", + "interrupt_controller", "interrupts", "log", "memory", diff --git a/Makefile b/Makefile index d6d1d219f1..1cf747dffd 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ ### This makefile is the top-level build script that builds all the crates in subdirectories ### and combines them into the final OS .iso image. ### It also provides convenient targets for running and debugging Theseus and using GDB on your host computer. -SHELL := /bin/bash +SHELL := /usr/bin/env bash ## Disable parallelism for this Makefile since it breaks the build, ## as our dependencies aren't perfectly specified for each target. diff --git a/applications/lspci/Cargo.toml b/applications/lspci/Cargo.toml new file mode 100644 index 0000000000..142434cc11 --- /dev/null +++ b/applications/lspci/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "lspci" +version = "0.1.0" +description = "An application which lists currently connected PCI devices." +authors = ["Nathan Royer "] +edition = "2021" + +[dependencies] +getopts = "0.2.21" +pci = { path = "../../kernel/pci" } +memory = { path = "../../kernel/memory" } +app_io = { path = "../../kernel/app_io" } diff --git a/applications/lspci/src/lib.rs b/applications/lspci/src/lib.rs new file mode 100644 index 0000000000..9ede0f89bf --- /dev/null +++ b/applications/lspci/src/lib.rs @@ -0,0 +1,75 @@ +//! This application lists currently connected PCI devices. + +#![no_std] + +extern crate alloc; +#[macro_use] extern crate app_io; +extern crate getopts; + +use alloc::vec::Vec; +use alloc::string::String; +use getopts::Options; +use pci::pci_device_iter; +use memory::PhysicalAddress; + +pub fn main(args: Vec) -> isize { + let mut opts = Options::new(); + opts.optflag("h", "help", "print this help menu"); + + let matches = match opts.parse(args) { + Ok(m) => m, + Err(_f) => { + println!("{}", _f); + print_usage(opts); + return -1; + } + }; + + if matches.opt_present("h") { + print_usage(opts); + return 0; + } + + if let Err(msg) = list_pci_devices() { + println!("Error: {}", msg); + } + + 0 +} + +fn list_pci_devices() -> Result<(), &'static str> { + for dev in pci_device_iter()? { + println!("{} -- {:04x}:{:04x}", dev.location, dev.vendor_id, dev.device_id); + println!("- class, subclass, prog_if: {:x}, {:x}, {:x}", dev.class, dev.subclass, dev.prog_if); + + for bar_idx in 0..6 { + let base = dev.determine_mem_base(bar_idx)?; + if base != PhysicalAddress::zero() { + let size = dev.determine_mem_size(bar_idx); + println!("- BAR {}: base = 0x{:x}, size = 0x{:x}", bar_idx, base, size); + } + } + + let support = dev.modern_interrupt_support(); + let supports = |b| match b { + true => "supported", + false => "not supported", + }; + + println!("- MSI interrupts: {}", supports(support.msi)); + println!("- MSI-X interrupts: {}", supports(support.msix)); + println!("- INTx enabled: {}", dev.pci_intx_enabled()); + println!("- INTx status: {}", dev.pci_get_intx_status(false)); + } + + Ok(()) +} + + +fn print_usage(opts: Options) { + println!("{}", opts.usage(USAGE)); +} + + +const USAGE: &str = "Usage: lspci +An application which lists currently connected PCI devices."; diff --git a/cfg/Config.mk b/cfg/Config.mk index f01a672d54..eee4008f1b 100644 --- a/cfg/Config.mk +++ b/cfg/Config.mk @@ -3,7 +3,7 @@ ### So, to access the directory containing this file, you would use "../" .DEFAULT_GOAL := all -SHELL := /bin/bash +SHELL := /usr/bin/env bash ## specifies which architecture we're building for ARCH ?= x86_64 diff --git a/kernel/device_manager/src/lib.rs b/kernel/device_manager/src/lib.rs index 98581b8d1a..49089c69f2 100644 --- a/kernel/device_manager/src/lib.rs +++ b/kernel/device_manager/src/lib.rs @@ -84,8 +84,6 @@ pub fn init( } } - pci::init()?; - // Initialize/scan the PCI bus to discover PCI devices for dev in pci::pci_device_iter()? { debug!("Found PCI device: {:X?}", dev); diff --git a/kernel/e1000/src/lib.rs b/kernel/e1000/src/lib.rs index 715e470d00..c6c272c74d 100644 --- a/kernel/e1000/src/lib.rs +++ b/kernel/e1000/src/lib.rs @@ -156,7 +156,7 @@ impl E1000Nic { //debug!("e1000_nc bar_type: {0}, mem_base: {1}, io_base: {2}", e1000_nc.bar_type, e1000_nc.mem_base, e1000_nc.io_base); // Get interrupt number - let interrupt_num = match e1000_pci_dev.pci_get_interrupt_info() { + let interrupt_num = match e1000_pci_dev.pci_get_intx_info() { Ok((Some(irq), _pin)) => (irq + IRQ_BASE_OFFSET) as InterruptNumber, _ => return Err("e1000: PCI device had no interrupt number (IRQ vector)"), }; diff --git a/kernel/interrupts/src/aarch64/mod.rs b/kernel/interrupts/src/aarch64/mod.rs index e423391771..29590df929 100644 --- a/kernel/interrupts/src/aarch64/mod.rs +++ b/kernel/interrupts/src/aarch64/mod.rs @@ -217,28 +217,6 @@ pub fn init_pl011_rx_interrupt() -> Result<(), &'static str> { int_ctrl.set_destination(PL011_RX_SPI, Some(current_cpu()), u8::MAX) } -/// Sets an interrupt handler for legacy PCI interrupts: INTA, INTB, INTC, INTD -pub fn init_pci_interrupts(handlers: [InterruptHandler; 4]) -> Result<(), &'static str> { - let int_ctrl = SystemInterruptController::get() - .ok_or("SystemInterruptController was not yet initialized")?; - let dst = Some(cpu::bootstrap_cpu().unwrap()); - - let pci_intx_nums = BOARD_CONFIG.pci_intx.into_iter(); - let pci_intx_handlers = handlers.into_iter(); - - for (int_num, handler) in pci_intx_nums.zip(pci_intx_handlers) { - if let Err(existing_handler) = register_interrupt(int_num, handler) { - if handler as InterruptHandler != existing_handler { - return Err("A different interrupt handler has already been setup for that PCI interrupt"); - } - } - - int_ctrl.set_destination(int_num, dst, u8::MAX)?; - } - - Ok(()) -} - /// Registers an interrupt handler at the given IRQ interrupt number. /// /// The function fails if the interrupt number is reserved or is already in use. diff --git a/kernel/pci/Cargo.toml b/kernel/pci/Cargo.toml index 869d260769..f0b57682cc 100644 --- a/kernel/pci/Cargo.toml +++ b/kernel/pci/Cargo.toml @@ -21,6 +21,7 @@ port_io = { path = "../../libs/port_io" } [target.'cfg(target_arch = "aarch64")'.dependencies] arm_boards = { path = "../arm_boards" } +interrupt_controller = { path = "../interrupt_controller" } [lib] crate-type = ["rlib"] diff --git a/kernel/pci/src/lib.rs b/kernel/pci/src/lib.rs index d8e8dbe92d..0406a70217 100644 --- a/kernel/pci/src/lib.rs +++ b/kernel/pci/src/lib.rs @@ -1,5 +1,20 @@ //! PCI Configuration Space Access //! +//! ## Terminology +//! +//! This crate deals with multiple types of interrupts: +//! * Legacy (INTx) interrupts are the oldest PCI interrupt representation. +//! * Four interrupt pins are shared among all devices. +//! * This crate refers to these legacy interrupts as "intx". +//! +//! * MSI (Message Signaled Interrupts) appeared with PCI express. +//! * They allow devices to allocate up to 32 interrupt numbers. +//! * This crate refers to these interrupts as "msi". +//! +//! * MSI-X messages appeared with PCIe 3.0. +//! * They allow devices to allocate up to 2048 interrupt numbers. +//! * This crate refers to these interrupts as "msix". +//! //! Note: while pci currently uses port-io on x86 and mmio on aarch64, //! x86 may also support memory-based PCI configuration in the future; //! port-io is the legacy way to access the config space. @@ -10,6 +25,7 @@ #![no_std] #![allow(dead_code)] +#![feature(abi_x86_interrupt)] extern crate alloc; @@ -22,15 +38,18 @@ use bit_field::BitField; use volatile::Volatile; use zerocopy::FromBytes; use cpu::CpuId; -use interrupts::InterruptNumber; +use interrupts::{InterruptNumber, InterruptHandler, interrupt_handler, register_interrupt, EoiBehaviour}; #[cfg(target_arch = "x86_64")] -use port_io::Port; +use { + port_io::Port, + interrupts::IRQ_BASE_OFFSET, +}; #[cfg(target_arch = "aarch64")] use { arm_boards::BOARD_CONFIG, - interrupts::{EoiBehaviour, interrupt_handler, init_pci_interrupts}, + interrupt_controller::{SystemInterruptController, SystemInterruptControllerApi}, }; #[derive(Debug, Copy, Clone)] @@ -217,33 +236,74 @@ pub fn pci_device_iter() -> Result, &'s } static INTX_DEVICES: Mutex> = Mutex::new(Vec::new()); - -// Architecture-independent PCI interrupt handler -// Currently aarch64-only, because legacy interrupts aren't supported on x86 yet. -#[cfg(target_arch = "aarch64")] -interrupt_handler!(pci_int_handler, None, _stack_frame, { - let devices = INTX_DEVICES.lock(); - - for device in &*devices { - if device.pci_get_interrupt_status(true) { - device.pci_enable_interrupts(false); - log::info!("Device {} triggered an interrupt", device.location); - - let reader = device.interrupt_waker.lock(); - match &*reader { - Some(waker) => waker.wake_by_ref(), - None => log::error!("Device doesn't have an interrupt waker!"), +static INTX_NUMBERS: Mutex<[Option; 4]> = Mutex::new([None; 4]); + +// Architecture-independent INTx handlers +macro_rules! intx_handler { + ($name:ident, $num:literal) => { + interrupt_handler!($name, { + let intx_numbers = INTX_NUMBERS.lock(); + intx_numbers[$num].expect("uninitialized x86 PCI INTx handler") + }, _stack_frame, { + let devices = INTX_DEVICES.lock(); + + for device in &*devices { + if device.pci_get_intx_status(true) { + device.pci_enable_intx(false); + log::info!("Device {} triggered a legacy interrupt", device.location); + + let reader = device.intx_waker.lock(); + match &*reader { + Some(waker) => waker.wake_by_ref(), + None => log::error!("Device doesn't have an interrupt waker!"), + } + } } + + EoiBehaviour::HandlerDidNotSendEoi + }); + }; +} + +intx_handler!(pci_intx_handler_1, 0); +intx_handler!(pci_intx_handler_2, 1); +intx_handler!(pci_intx_handler_3, 2); +intx_handler!(pci_intx_handler_4, 3); + +static HANDLERS: [InterruptHandler; 4] = [pci_intx_handler_1, pci_intx_handler_2, pci_intx_handler_3, pci_intx_handler_4]; + +// Ensures that a PCI INTx interrupt handler is registered for this interrupt number +fn init_intx_handler(int_num: InterruptNumber) -> Result<(), &'static str> { + let mut intx_numbers = INTX_NUMBERS.lock(); + let mut slot = None; + + for i in 0..4 { + if intx_numbers[i] == Some(int_num) { + return Ok((/* this interrupt number was already initialized. */)); + } else if intx_numbers[i].is_none() { + slot = Some(i); } } - EoiBehaviour::HandlerDidNotSendEoi -}); + let slot = slot.ok_or("More than four different INTx numbers were encountered")?; + intx_numbers[slot] = Some(int_num); + core::mem::drop(intx_numbers); -/// Initializes the PCI interrupt handler -pub fn init() -> Result<(), &'static str> { - #[cfg(target_arch = "aarch64")] - init_pci_interrupts([pci_int_handler; 4])?; + let pci_intx_handler = HANDLERS[slot]; + if let Err(existing_handler) = register_interrupt(int_num, pci_intx_handler) { + if existing_handler != (pci_intx_handler as _) { + return Err("Couldn't lazily set PCI INTx handler"); + } + } + + #[cfg(target_arch = "aarch64")] { + let int_dst = Some(cpu::bootstrap_cpu().unwrap()); + let int_ctrl = SystemInterruptController::get() + .ok_or("SystemInterruptController was not yet initialized")?; + + // route interrupt to bootstrap processor + int_ctrl.set_destination(int_num, int_dst, u8::MAX)?; + } Ok(()) } @@ -326,11 +386,11 @@ fn scan_pci() -> Result, &'static str> { int_pin: location.pci_read_8(PCI_INTERRUPT_PIN), int_line: location.pci_read_8(PCI_INTERRUPT_LINE), location, - interrupt_waker: Mutex::new(None), + intx_waker: Mutex::new(None), }; // disable legacy interrupts initially - device.pci_enable_interrupts(false); + device.pci_enable_intx(false); device_list.push(device); } @@ -550,20 +610,15 @@ impl PciLocation { } /// Sets the PCI device's command bit 10 to disable legacy interrupts - pub fn pci_set_interrupt_disable_bit(&self, bit: bool) { + pub fn pci_set_intx_disable_bit(&self, bit: bool) { let command = self.pci_read_16(PCI_COMMAND); - // trace!("pci_set_interrupt_disable_bit: PciDevice: {}, read value: {:#x}", self, command); let new_value = match bit { true => command | PCI_COMMAND_INT_DISABLED, false => command & !PCI_COMMAND_INT_DISABLED, }; - self.pci_write_16(PCI_COMMAND, new_value); - /*trace!("pci_set_interrupt_disable_bit: PciDevice: {} read value AFTER WRITE CMD: {:#x}", - self, - self.pci_read_16(PCI_COMMAND), - );*/ + self.pci_write_16(PCI_COMMAND, new_value); } /// Explores the PCI config space and returns address of requested capability, if present. @@ -625,6 +680,13 @@ impl fmt::Debug for PciLocation { } } +/// Returned by [`PciDevice::modern_interrupt_support`] +pub struct ModernInterruptSupport { + /// `true` if this device supports MSI (Message Signaled Interrupts) + pub msi: bool, + /// `true` if this device supports MSI-X + pub msix: bool, +} /// Contains information common to every type of PCI Device, /// and offers functions for reading/writing to the PCI configuration space. @@ -637,7 +699,7 @@ pub struct PciDevice { pub location: PciLocation, /// The handling task for legacy PCI interrupts - pub interrupt_waker: Mutex>, + pub intx_waker: Mutex>, /// The class code, used to determine device type. pub class: u8, @@ -731,6 +793,14 @@ impl PciDevice { mem_size } + /// Queries and returns whether this PCI device supports MSI and MSI-X interrupts. + pub fn modern_interrupt_support(&self) -> ModernInterruptSupport { + ModernInterruptSupport { + msi: self.find_pci_capability(PciCapability::Msi).is_some(), + msix: self.find_pci_capability(PciCapability::Msix).is_some(), + } + } + /// Enable MSI interrupts for a PCI device. /// We assume the device only supports one MSI vector /// and set the interrupt number and core id for that vector. @@ -859,10 +929,10 @@ impl PciDevice { map_frame_range(mem_base, mem_size as usize, MMIO_FLAGS) } - /// Reads and returns this PCI device's interrupt line and interrupt pin registers. + /// Reads and returns this PCI device's INTx line and INTx pin registers. /// - /// Returns an error if this PCI device's interrupt pin value is invalid (greater than 4). - pub fn pci_get_interrupt_info(&self) -> Result<(Option, Option), &'static str> { + /// Returns an error if this PCI device's INTx pin value is invalid (greater than 4). + pub fn pci_get_intx_info(&self) -> Result<(Option, Option), &'static str> { let int_line = match self.pci_read_8(PCI_INTERRUPT_LINE) { 0xff => None, other => Some(other), @@ -881,25 +951,54 @@ impl PciDevice { } /// Enables/Disables legacy (INTx) interrupts for this device - pub fn pci_enable_interrupts(&self, enable: bool) { - self.pci_set_interrupt_disable_bit(!enable); + pub fn pci_enable_intx(&self, enable: bool) { + self.pci_set_intx_disable_bit(!enable); } - /// Reads and returns this PCI device's interrupt status flag. - pub fn pci_get_interrupt_status(&self, check_enabled: bool) -> bool { - const PCI_STATUS_INT: u16 = 1 << 3; + /// Checks that legacy interrupts are enabled in the command register. + pub fn pci_intx_enabled(&self) -> bool { + (self.pci_read_16(PCI_COMMAND) & PCI_COMMAND_INT_DISABLED) == 0 + } - let interrupt_enabled = || (self.pci_read_16(PCI_COMMAND) & PCI_COMMAND_INT_DISABLED) == 0; - let pending_interrupt = || (self.pci_read_16(PCI_STATUS) & PCI_STATUS_INT ) != 0; + /// Reads and returns this PCI device's legacy interrupt status flag. + pub fn pci_get_intx_status(&self, check_enabled: bool) -> bool { + const PCI_STATUS_INT: u16 = 1 << 3; + let pending_interrupt = || (self.pci_read_16(PCI_STATUS) & PCI_STATUS_INT) != 0; - ((!check_enabled) || interrupt_enabled()) && pending_interrupt() + ((!check_enabled) || self.pci_intx_enabled()) && pending_interrupt() } - /// Sets a task waker to be used when this device triggers an interrupt + /// Sets up the given `waker` to be woken when this PCI device triggers a legacy interrupt (INTx). /// /// Returns the previous interrupt waker for this device, if there was one. - pub fn set_interrupt_waker(&'static self, waker: Waker) -> Option { - let mut handle = self.interrupt_waker.lock(); + pub fn set_intx_waker(&'static self, waker: Waker) -> Result, &'static str> { + + // On x86, we don't yet support querying the ACPI tables to properly determine + // which interrupt numbers are used for legacy PCI interrupts. + // As a workaround, we lazily register these handlers when a driver + // calls this function, as by that time we're sure that + // the interrupt number in the device's config space is correct. + #[cfg(target_arch = "x86_64")] { + let int_num = match self.pci_get_intx_info() { + Ok((Some(irq), _pin)) => (irq + IRQ_BASE_OFFSET) as InterruptNumber, + _ => { + log::error!("Failed to get INTx info for PCI device {:?}", self); + return Err("PciDevice::set_intx_waker() failed to get INTx info"); + } + }; + + init_intx_handler(int_num)?; + } + + // On aarch64, we *do* know the interrupt numbers statically, + // but we do it lazily anyway for the sake of code clarity. + #[cfg(target_arch = "aarch64")] { + for int_num in BOARD_CONFIG.pci_intx { + init_intx_handler(int_num)?; + } + } + + let mut handle = self.intx_waker.lock(); let prev_value = handle.replace(waker); if prev_value.is_none() { @@ -907,7 +1006,7 @@ impl PciDevice { intx_devices.push(self) } - prev_value + Ok(prev_value) } } diff --git a/libs/bit_set/Cargo.toml b/libs/bit_set/Cargo.toml index c0586429dc..af52627a32 100644 --- a/libs/bit_set/Cargo.toml +++ b/libs/bit_set/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bit_set" version = "0.1.0" +authors = ["Klim Tsoutsman "] +description = "A bit set storing integers less than 64" edition = "2021" - -[dependencies] diff --git a/libs/bit_set/src/iter.rs b/libs/bit_set/src/iter.rs index fd5d484d68..135f36c3c2 100644 --- a/libs/bit_set/src/iter.rs +++ b/libs/bit_set/src/iter.rs @@ -1,10 +1,8 @@ -//! An iterator over a bit set. -//! -//! See [`Iter`] for more details. - use core::intrinsics::unlikely; -/// An iterator over a bit set. +/// An iterator over a [`BitSet`]. +/// +/// [`BitSet`]: crate::BitSet pub struct Iter { set: u64, current_mask: u64, diff --git a/libs/bit_set/src/lib.rs b/libs/bit_set/src/lib.rs index 23bc4a0301..575ce64a90 100644 --- a/libs/bit_set/src/lib.rs +++ b/libs/bit_set/src/lib.rs @@ -1,3 +1,7 @@ +//! A bit set backed by a [`u64`]. +//! +//! See [`BitSet`] for more details. + #![no_std] #![feature(const_likely, core_intrinsics)]