Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PCI & interrupts modifications related to USB #1071

Merged
merged 42 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
53bec8c
Port of the `pci` crate
NathanRoyer Jul 25, 2023
d716bd7
Allow clippy::unnecessary_cast caused by platform abstraction
NathanRoyer Jul 25, 2023
66e4ddb
Document Makefile edits
NathanRoyer Jul 25, 2023
ee27c46
`pci` doc
NathanRoyer Jul 25, 2023
84860ad
Use volatile read/writes in `pci` [aarch64]
NathanRoyer Jul 27, 2023
8ae8816
Store the PCI config space in Once on aarch64
NathanRoyer Jul 27, 2023
68c618e
Remove unneeded qemu flags
NathanRoyer Jul 27, 2023
d6f72b3
Merge remote-tracking branch 'theseus-os/theseus_main' into pci-port
NathanRoyer Aug 1, 2023
aec3809
fix: allocate the PCI config space as device memory
NathanRoyer Aug 4, 2023
be56bc6
Improve pci_address_and_shift (documentation + structs)
NathanRoyer Aug 4, 2023
fb937fa
Use MMIO_FLAGS in aarch64-specific PCI code
NathanRoyer Aug 4, 2023
c12a3e4
Merge remote-tracking branch 'theseus-os/theseus_main' into usb-ehci
NathanRoyer Aug 21, 2023
fdc8a69
EHCI: got a keyboard device descriptor
NathanRoyer Aug 21, 2023
fe4e868
Prepare USB Allocator structure
NathanRoyer Aug 22, 2023
26ef6dd
Merge remote-tracking branch 'theseus-os/theseus_main' into usb-ehci
NathanRoyer Sep 26, 2023
99f1850
Improve EHCI driver structure
NathanRoyer Oct 6, 2023
998ddde
Define all standard descriptor types
NathanRoyer Oct 11, 2023
dfd7766
Nice API for standard requests
NathanRoyer Oct 19, 2023
5484a98
Read USB keyboard state using control transfers
NathanRoyer Oct 31, 2023
4085bb5
Handle PCI interrupts by detecting which device caused them
NathanRoyer Nov 1, 2023
a88b5fe
Detect keystrokes using interrupt transfers
NathanRoyer Nov 1, 2023
9d70cc2
Re-organize USB work into one crate
NathanRoyer Nov 2, 2023
7c34a9d
Better USB allocation types
NathanRoyer Nov 4, 2023
d9d9202
Fix STATUS stage of EHCI requests
NathanRoyer Nov 6, 2023
3c983d1
New ControllerApi / InterfaceApi traits
NathanRoyer Nov 6, 2023
e3e58ec
Added support for legacy interrupt dispatch in `pci`
NathanRoyer Nov 7, 2023
64cdae2
Handle USB EHCI interrupts
NathanRoyer Nov 7, 2023
a52bf0a
Improve PCI/USB interface
NathanRoyer Nov 8, 2023
fce5474
Restrict visibility of USB internals
NathanRoyer Nov 8, 2023
f0dfb3d
Remove sketchy macros
NathanRoyer Nov 8, 2023
d833752
Remove USB crate
NathanRoyer Nov 8, 2023
2511eb2
More reverts
NathanRoyer Nov 8, 2023
eb2595b
PCI for USB: improvements
NathanRoyer Nov 21, 2023
37f911c
PCI for USB: API fixes
NathanRoyer Nov 21, 2023
7948c06
Move PCI interrupt numbers to arm_boards
NathanRoyer Nov 21, 2023
ac87fac
Resolve conflict (generic timer was moved to its own crate)
NathanRoyer Nov 21, 2023
0b58f17
Merge branch 'theseus_main' into usb-premod
NathanRoyer Nov 21, 2023
3aa2ddf
Clippy Lint
NathanRoyer Nov 21, 2023
5c487ea
Merge fixes
NathanRoyer Nov 21, 2023
4949795
Improve PCI interrupt handler registration function
NathanRoyer Dec 6, 2023
9473e75
Improved PCI crate doc, replaced RwLocks with Mutexes
NathanRoyer Dec 6, 2023
2467383
don't panic within PCI interrupt handler
kevinaboos Dec 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is also confusing, as upon first glance, the reader of this code would expect that you're just temporarily disabling PCI interrupts here while you handle the interrupt and then re-enabling them later. However, that doesn't happen, so this must mean something else.

We should rename and properly document the pci_enable_interrupts() function to be more clear about how and why it should be invoked (e.g., perhaps we just always want to disable this type of interrupt?)

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!"),
NathanRoyer marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should rename this function to be more clear about what it actually does, and use the standard "enable_xyz" terminology. We can mention what it actually does (i.e., set the disable bit) in the function documentation, but as it stands now it's quite difficult to understand.

I'll accept this now; kindly include that in the next PR about PCI/USB. 👍

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