Skip to content

Commit

Permalink
aarch64: support PCI INTx legacy interrupts USB (theseus-os#1071)
Browse files Browse the repository at this point in the history
* Set up a default handler for legacy PCI interrupts (e.g., on PIN A, B, C, D);
  their handler wakes up a waker (task) that was previously bound to the
  `PciDevice` that received the interrupt.

* Only aarch64 is supported at the moment.

* This PR is needed to support USB interrupt handling.

Co-authored-by: Kevin Boos <[email protected]>
  • Loading branch information
NathanRoyer and kevinaboos authored Dec 12, 2023
1 parent 3a678a9 commit 1e2c7df
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 18 deletions.
1 change: 1 addition & 0 deletions kernel/arm_boards/src/boards/qemu_virt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub const BOARD_CONFIG: BoardConfig = BoardConfig {
pl011_base_addresses: [ PhysicalAddress::new_canonical(0x09000000) ],
pl011_rx_spi: 33,
cpu_local_timer_ppi: 30,
pci_intx: [35, 36, 37, 38],

// obtained via internal qemu debugging
// todo: will this always be correct?
Expand Down
3 changes: 3 additions & 0 deletions kernel/arm_boards/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub struct BoardConfig {
// aarch64 manuals define the default timer IRQ number to be 30.
pub cpu_local_timer_ppi: u8,

/// The IRQ numbers reserved for legacy PCI interrupts: INTA, INTB, INTC and INTD.
pub pci_intx: [u8; 4],

pub pci_ecam: PciEcamConfig,
}

Expand Down
28 changes: 19 additions & 9 deletions kernel/device_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

extern crate alloc;

use log::{info, debug};
use log::*;

#[cfg(target_arch = "x86_64")]
use {
log::{error, warn},
mpmc::Queue,
event_types::Event,
memory::MemoryManagementInfo,
Expand Down Expand Up @@ -81,15 +80,16 @@ pub fn init(
mouse::init(ps2_controller.mouse_ref(), mouse_producer)?;
}

pci::init()?;

// Initialize/scan the PCI bus to discover PCI devices
for dev in pci::pci_device_iter()? {
debug!("Found PCI device: {:X?}", dev);
}

// No NIC support on aarch64 at the moment
#[cfg(target_arch = "x86_64")] {

// store all the initialized ixgbe NICs here to be added to the network interface list
// No NIC support on aarch64 at the moment
#[cfg(target_arch = "x86_64")]
let mut ixgbe_devs = Vec::new();

// Iterate over all PCI devices and initialize the drivers for the devices we support.
Expand All @@ -101,6 +101,8 @@ pub fn init(
}

// If this is a storage device, initialize it as such.
// No storage device support on aarch64 at the moment
#[cfg(target_arch = "x86_64")]
match storage_manager::init_device(dev) {
// Successfully initialized this storage device.
Ok(Some(_storage_controller)) => continue,
Expand All @@ -117,6 +119,8 @@ pub fn init(

// If this is a network device, initialize it as such.
// Look for networking controllers, specifically ethernet cards
// No NIC support on aarch64 at the moment
#[cfg(target_arch = "x86_64")]
if dev.class == 0x02 && dev.subclass == 0x00 {
if dev.vendor_id == e1000::INTEL_VEND && dev.device_id == e1000::E1000_DEV {
info!("e1000 PCI device found at: {:?}", dev.location);
Expand Down Expand Up @@ -167,18 +171,25 @@ pub fn init(
}

// Once all the NICs have been initialized, we can store them and add them to the list of network interfaces.
let ixgbe_nics = ixgbe::IXGBE_NICS.call_once(|| ixgbe_devs);
for ixgbe_nic_ref in ixgbe_nics.iter() {
net::register_device(ixgbe_nic_ref);
// No NIC support on aarch64 at the moment
#[cfg(target_arch = "x86_64")] {
let ixgbe_nics = ixgbe::IXGBE_NICS.call_once(|| ixgbe_devs);
for ixgbe_nic_ref in ixgbe_nics.iter() {
net::register_device(ixgbe_nic_ref);
}
}

// Convenience notification for developers to inform them of no networking devices
// No NIC support on aarch64 at the moment
#[cfg(target_arch = "x86_64")]
if net::get_default_interface().is_none() {
warn!("Note: no network devices found on this system.");
}

// Discover filesystems from each storage device on the storage controllers initialized above
// and mount each filesystem to the root directory by default.
// No storage device support on aarch64 at the moment
#[cfg(target_arch = "x86_64")]
if false {
for storage_device in storage_manager::storage_devices() {
let disk = fatfs_adapter::FatFsAdapter::new(
Expand Down Expand Up @@ -213,7 +224,6 @@ pub fn init(
}
}
}
}

Ok(())
}
Expand Down
21 changes: 21 additions & 0 deletions kernel/interrupts/src/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,27 @@ 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.
///
Expand Down
4 changes: 3 additions & 1 deletion kernel/ixgbe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,10 @@ impl IxgbeNic {
// enable msi-x interrupts if required and return the assigned interrupt numbers
let interrupt_num =
if let Some(interrupt_handlers) = interrupts {
// no need to disable legacy interrupts, it was done during device initialization.
// ixgbe_pci_dev.pci_set_interrupt_disable_bit(true);

ixgbe_pci_dev.pci_enable_msix()?;
ixgbe_pci_dev.pci_set_interrupt_disable_bit();
Self::enable_msix_interrupts(&mut mapped_registers1, &mut rx_queues, &mut vector_table, &interrupt_handlers)?
}
else {
Expand Down
96 changes: 88 additions & 8 deletions kernel/pci/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
//! 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.
//!
//! For context on the various interrupt mechanisms (MSI/MSI-X/INTx):
//! - [this StackExchange reply](https://electronics.stackexchange.com/a/343218)
//! - PCI Express Base Specification, Revision 2, Chapter 6.1 - Interrupt & PME Support
#![no_std]
#![allow(dead_code)]

extern crate alloc;

use log::*;
use core::{fmt, ops::{Deref, DerefMut}, mem::size_of};
use core::{fmt, ops::{Deref, DerefMut}, mem::size_of, task::Waker};
use alloc::vec::Vec;
use spin::{Once, Mutex};
use memory::{PhysicalAddress, BorrowedSliceMappedPages, Mutable, MappedPages, map_frame_range, MMIO_FLAGS};
Expand All @@ -24,7 +28,10 @@ use interrupts::InterruptNumber;
use port_io::Port;

#[cfg(target_arch = "aarch64")]
use arm_boards::BOARD_CONFIG;
use {
arm_boards::BOARD_CONFIG,
interrupts::{EoiBehaviour, interrupt_handler, init_pci_interrupts},
};

#[derive(Debug, Copy, Clone)]
/// The span of bytes within a 4-byte chunk that a PCI register occupies.
Expand Down Expand Up @@ -121,6 +128,8 @@ pci_register!(PCI_INTERRUPT_PIN, 0x3D, 1);
pci_register!(PCI_MIN_GRANT, 0x3E, 1);
pci_register!(PCI_MAX_LATENCY, 0x3F, 1);

const PCI_COMMAND_INT_DISABLED: u16 = 1 << 10;

#[repr(u8)]
pub enum PciCapability {
Msi = 0x05,
Expand Down Expand Up @@ -207,6 +216,37 @@ pub fn pci_device_iter() -> Result<impl Iterator<Item = &'static PciDevice>, &'s
Ok(get_pci_buses()?.iter().flat_map(|b| b.devices.iter()))
}

static INTX_DEVICES: Mutex<Vec<&'static PciDevice>> = 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!"),
}
}
}

EoiBehaviour::HandlerDidNotSendEoi
});

/// Initializes the PCI interrupt handler
pub fn init() -> Result<(), &'static str> {
#[cfg(target_arch = "aarch64")]
init_pci_interrupts([pci_int_handler; 4])?;

Ok(())
}

/// A PCI bus, which contains a list of PCI devices on that bus.
#[derive(Debug)]
Expand Down Expand Up @@ -286,8 +326,12 @@ fn scan_pci() -> Result<Vec<PciBus>, &'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),
};

// disable legacy interrupts initially
device.pci_enable_interrupts(false);

device_list.push(device);
}
}
Expand Down Expand Up @@ -506,17 +550,20 @@ impl PciLocation {
}

/// Sets the PCI device's command bit 10 to disable legacy interrupts
pub fn pci_set_interrupt_disable_bit(&self) {
pub fn pci_set_interrupt_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);
// trace!("pci_set_interrupt_disable_bit: PciDevice: {}, read value: {:#x}", self, command);

const INTERRUPT_DISABLE: u16 = 1 << 10;
self.pci_write_16(PCI_COMMAND, command | INTERRUPT_DISABLE);
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}",
/*trace!("pci_set_interrupt_disable_bit: PciDevice: {} read value AFTER WRITE CMD: {:#x}",
self,
self.pci_read_16(PCI_COMMAND),
);
);*/
}

/// Explores the PCI config space and returns address of requested capability, if present.
Expand Down Expand Up @@ -589,6 +636,9 @@ pub struct PciDevice {
/// the bus, slot, and function number that locates this PCI device in the bus tree.
pub location: PciLocation,

/// The handling task for legacy PCI interrupts
pub interrupt_waker: Mutex<Option<Waker>>,

/// The class code, used to determine device type.
pub class: u8,
/// The subclass code, used to determine device type.
Expand Down Expand Up @@ -829,6 +879,36 @@ impl PciDevice {

Ok((int_line, int_pin))
}

/// Enables/Disables legacy (INTx) interrupts for this device
pub fn pci_enable_interrupts(&self, enable: bool) {
self.pci_set_interrupt_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;

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;

((!check_enabled) || interrupt_enabled()) && pending_interrupt()
}

/// Sets a task waker to be used when this device triggers an interrupt
///
/// Returns the previous interrupt waker for this device, if there was one.
pub fn set_interrupt_waker(&'static self, waker: Waker) -> Option<Waker> {
let mut handle = self.interrupt_waker.lock();
let prev_value = handle.replace(waker);

if prev_value.is_none() {
let mut intx_devices = INTX_DEVICES.lock();
intx_devices.push(self)
}

prev_value
}
}

impl Deref for PciDevice {
Expand Down

0 comments on commit 1e2c7df

Please sign in to comment.