From 4f62888bb40b6c936d06d1a1741abf1a177e9e06 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 21 Apr 2020 15:42:29 +0200 Subject: [PATCH] Various fixes. --- pcid/src/driver_interface/irq_helpers.rs | 137 ++++++++++++++++++ .../mod.rs} | 11 +- pcid/src/main.rs | 2 +- xhcid/src/main.rs | 92 +++++------- 4 files changed, 183 insertions(+), 59 deletions(-) create mode 100644 pcid/src/driver_interface/irq_helpers.rs rename pcid/src/{driver_interface.rs => driver_interface/mod.rs} (95%) diff --git a/pcid/src/driver_interface/irq_helpers.rs b/pcid/src/driver_interface/irq_helpers.rs new file mode 100644 index 0000000000..58d252be7a --- /dev/null +++ b/pcid/src/driver_interface/irq_helpers.rs @@ -0,0 +1,137 @@ +//! IRQ helpers. +//! +//! This module allows easy handling of the `irq:` scheme, and allocating interrupt vectors for use +//! by INTx#, MSI, or MSI-X. + +use std::fs::{self, File}; +use std::io::{self, prelude::*}; +use std::num::NonZeroU8; +use std::ops; + +/// Read the local APIC ID of the bootstrap processor. +pub fn read_bsp_apic_id() -> io::Result { + let mut buffer = [0u8; 8]; + + let mut file = File::open("irq:bsp")?; + let bytes_read = file.read(&mut buffer)?; + + Ok(if bytes_read == 8 { + u64::from_le_bytes(buffer) as u32 + } else if bytes_read == 4 { + u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) + } else { + panic!("`irq:` scheme responded with {} bytes, expected {}", bytes_read, std::mem::size_of::()); + }) +} + +/// Allocate multiple interrupt vectors, from the IDT of the bootstrap processor, returning the +/// start vector and the IRQ handles. +/// +/// The alignment is a requirement for the allocation range. For example, with an alignment of 8, +/// only ranges that begin with a multiple of eight are accepted. The IRQ handles returned will +/// always correspond to the subsequent IRQ numbers beginning the first value in the return tuple. +/// +/// This function is not actually guaranteed to allocate all of the IRQs specified in `count`, +/// since another process might already have requested that vector. The caller must check that +/// the returned vector have the same length as `count`. In the future this function may perhaps +/// lock the entire directory to prevent this from happening, or maybe find the smallest free range +/// with the minimum alignment, to allow other drivers to obtain their necessary IRQs. +/// +/// Note that this count/alignment restriction is only mandatory for MSI; MSI-X allows for +/// individually allocated vectors that might be spread out, even on multiple CPUs. Thus, multiple +/// invocations with alignment 1 and count 1 are totally acceptable, although allocating in bulk +/// minimizes the initialization overhead, even though it's negligible. +pub fn allocate_aligned_interrupt_vectors(alignment: NonZeroU8, count: u8) -> io::Result)>> { + if count == 0 { return Ok(None) } + + let available_irqs = fs::read_dir("irq:")?; + let mut available_irq_numbers = available_irqs.filter_map(|entry| -> Option> { + let entry = match entry { + Ok(e) => e, + Err(err) => return Some(Err(err)), + }; + + let path = entry.path(); + + let file_name = match path.file_name() { + Some(f) => f, + None => return None, + }; + + let path_str = match file_name.to_str() { + Some(s) => s, + None => return None, + }; + + // note that there might be future subdirectories in the IRQ scheme, such as `cpu-/`, thus no error but just None + match path_str.parse::() { + Ok(p) => Some(Ok(p)), + Err(_) => None, + } + }); + + // TODO: fcntl F_SETLK on `irq:/`? + + let mut handles = Vec::with_capacity(usize::from(count)); + + let mut index = 0; + let mut first = None; + + while let Some(number) = available_irq_numbers.next() { + let number = number?; + + // Skip until a suitable alignment is found. + if number % u8::from(alignment) != 0 { + continue; + } + let first = *first.get_or_insert(number); + let irq_number = first + index; + + // From the point where the range is aligned, we can start to advance until `count` IRQs + // have been allocated. + if index >= count { + break; + } + + // if found, reserve the irq + let irq_handle = match File::create(format!("irq:{}", irq_number)) { + Ok(handle) => handle, + + // return early if the entire range couldn't be allocated + Err(err) if err.kind() == io::ErrorKind::NotFound => break, + + Err(err) => return Err(err), + }; + handles.push(irq_handle); + index += 1; + } + if handles.is_empty() { + return Ok(None); + } + let first = match first { + Some(f) => f, + None => return Ok(None), + }; + + Ok(Some((first + 32, handles))) +} + +/// Allocate at most `count` interrupt vectors, which can start at any offset. Unless MSI is used +/// and an entire aligned range of vectors is needed, this function should be used. +pub fn allocate_interrupt_vectors(count: u8) -> io::Result)>> { + allocate_aligned_interrupt_vectors(NonZeroU8::new(1).unwrap(), count) +} + +/// Allocate a single interrupt vector, returning both the vector number (starting from 32 up to +/// 254), and its IRQ handle which is then reserved. Returns Ok(None) if allocation fails due to +/// no available IRQs. +pub fn allocate_single_interrupt_vector() -> io::Result> { + let (base, mut files) = match allocate_interrupt_vectors(1) { + Ok(Some((base, files))) => (base, files), + Ok(None) => return Ok(None), + Err(err) => return Err(err), + }; + assert_eq!(files.len(), 1); + Ok(Some((base, files.pop().unwrap()))) +} diff --git a/pcid/src/driver_interface.rs b/pcid/src/driver_interface/mod.rs similarity index 95% rename from pcid/src/driver_interface.rs rename to pcid/src/driver_interface/mod.rs index 12a055d666..e301bbe597 100644 --- a/pcid/src/driver_interface.rs +++ b/pcid/src/driver_interface/mod.rs @@ -10,6 +10,8 @@ use thiserror::Error; pub use crate::pci::PciBar; pub use crate::pci::msi; +pub mod irq_helpers; + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[repr(u8)] pub enum LegacyInterruptPin { @@ -118,7 +120,7 @@ pub type Result = std::result::Result; // TODO: Remove these "features" and just go strait to the actual thing. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct MsiSetFeatureInfo { /// The Multi Message Enable field of the Message Control in the MSI Capability Structure, /// is the log2 of the interrupt vectors, minus one. Can only be 0b000..=0b101. @@ -273,4 +275,11 @@ impl PcidServerHandle { other => Err(PcidClientHandleError::InvalidResponse(other)), } } + pub fn set_feature_info(&mut self, info: SetFeatureInfo) -> Result<()> { + self.send(&PcidClientRequest::SetFeatureInfo(info))?; + match self.recv()? { + PcidClientResponse::SetFeatureInfo(_) => Ok(()), + other => Err(PcidClientHandleError::InvalidResponse(other)), + } + } } diff --git a/pcid/src/main.rs b/pcid/src/main.rs index 4459185018..f5b0d83ec9 100644 --- a/pcid/src/main.rs +++ b/pcid/src/main.rs @@ -195,7 +195,7 @@ impl State { fn handle_parsed_header(state: Arc, config: &Config, bus_num: u8, dev_num: u8, func_num: u8, header: PciHeader) { - let pci = &state.pci; + let pci = state.preferred_cfg_access(); let raw_class: u8 = header.class().into(); let mut string = format!("PCI {:>02X}/{:>02X}/{:>02X} {:>04X}:{:>04X} {:>02X}.{:>02X}.{:>02X}.{:>02X} {:?}", diff --git a/xhcid/src/main.rs b/xhcid/src/main.rs index d475b453c5..8b9c38c026 100644 --- a/xhcid/src/main.rs +++ b/xhcid/src/main.rs @@ -11,7 +11,8 @@ use std::ptr::NonNull; use std::sync::{Arc, Mutex}; use std::env; -use pcid_interface::{PcidServerHandle, PciFeature, PciFeatureInfo}; +use pcid_interface::{MsiSetFeatureInfo, PcidServerHandle, PciFeature, PciFeatureInfo, SetFeatureInfo}; +use pcid_interface::irq_helpers::{read_bsp_apic_id, allocate_single_interrupt_vector}; use pcid_interface::msi::{MsiCapability, MsixCapability, MsixTableEntry}; use event::{Event, EventQueue}; @@ -32,49 +33,6 @@ pub mod driver_interface; mod usb; mod xhci; -/// Read the local APIC id of the bootstrap processor. -fn read_bsp_apic_id() -> io::Result { - let mut buffer = [0u8; 8]; - - let mut file = File::open("irq:bsp")?; - let bytes_read = file.read(&mut buffer)?; - - Ok(if bytes_read == 8 { - u64::from_le_bytes(buffer) as u32 - } else if bytes_read == 4 { - u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) - } else { - panic!("`irq:` scheme responded with {} bytes, expected {}", bytes_read, std::mem::size_of::()); - }) -} -/// Allocate an interrupt vector, located at the BSP's IDT. -fn allocate_interrupt_vector() -> io::Result> { - let available_irqs = fs::read_dir("irq:")?; - - for entry in available_irqs { - let entry = entry?; - let path = entry.path(); - - let file_name = match path.file_name() { - Some(f) => f, - None => continue, - }; - - let path_str = match file_name.to_str() { - Some(s) => s, - None => continue, - }; - - if let Ok(irq_number) = path_str.parse::() { - // if found, reserve the irq - let irq_handle = File::create(format!("irq:{}", irq_number))?; - let interrupt_vector = irq_number + 32; - return Ok(Some((interrupt_vector, irq_handle))); - } - } - Ok(None) -} - async fn handle_packet(hci: Arc, packet: Packet) -> Packet { todo!() } @@ -124,29 +82,44 @@ fn main() { let (has_msi, mut msi_enabled) = all_pci_features.iter().map(|(feature, status)| (feature.is_msi(), status.is_enabled())).find(|&(f, _)| f).unwrap_or((false, false)); let (has_msix, mut msix_enabled) = all_pci_features.iter().map(|(feature, status)| (feature.is_msix(), status.is_enabled())).find(|&(f, _)| f).unwrap_or((false, false)); - dbg!(has_msi, msi_enabled); - dbg!(has_msix, msix_enabled); - if has_msi && !msi_enabled && !has_msix { - pcid_handle.enable_feature(PciFeature::Msi).expect("xhcid: failed to enable MSI"); - info!("Enabled MSI"); msi_enabled = true; } if has_msix && !msix_enabled { - pcid_handle.enable_feature(PciFeature::MsiX).expect("xhcid: failed to enable MSI-X"); - info!("Enabled MSI-X"); msix_enabled = true; } let (mut irq_file, interrupt_method) = if msi_enabled && !msix_enabled { + use pcid_interface::msi::x86_64::{DeliveryMode, self as x86_64_msix}; + let mut capability = match pcid_handle.feature_info(PciFeature::MsiX).expect("xhcid: failed to retrieve the MSI capability structure from pcid") { PciFeatureInfo::Msi(s) => s, PciFeatureInfo::MsiX(_) => panic!(), }; - // use one vector - capability.set_multi_message_enable(0); + // TODO: Allow allocation of up to 32 vectors. - todo!("msi (msix is implemented though)") + // TODO: Find a way to abstract this away, potantially as a helper module for + // pcid_interface, so that this can be shared between nvmed, xhcid, ixgebd, etc.. + + let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id"); + let msg_addr = x86_64_msix::message_address(destination_id as u8, false, false, 0b00); + + let (vector, interrupt_handle) = allocate_single_interrupt_vector().expect("xhcid: failed to allocate interrupt vector").expect("xhcid: no interrupt vectors left"); + let msg_data = x86_64_msix::message_data_edge_triggered(DeliveryMode::Fixed, vector); + + let set_feature_info = MsiSetFeatureInfo { + multi_message_enable: Some(0), + message_address: Some(msg_addr), + message_upper_address: Some(0), + message_data: Some(msg_data as u16), + mask_bits: None, + }; + pcid_handle.set_feature_info(SetFeatureInfo::Msi(set_feature_info)).expect("xhcid: failed to set feature info"); + + pcid_handle.enable_feature(PciFeature::Msi).expect("xhcid: failed to enable MSI"); + info!("Enabled MSI"); + + (Some(interrupt_handle), InterruptMethod::Msi) } else if msix_enabled { let capability = match pcid_handle.feature_info(PciFeature::MsiX).expect("xhcid: failed to retrieve the MSI-X capability structure from pcid") { PciFeatureInfo::Msi(_) => panic!(), @@ -178,7 +151,7 @@ fn main() { // Allocate one msi vector. - { + let method = { use pcid_interface::msi::x86_64::{DeliveryMode, self as x86_64_msix}; // primary interrupter @@ -192,7 +165,7 @@ fn main() { let dm = false; let addr = x86_64_msix::message_address(destination_id.try_into().expect("xhcid: BSP apic id couldn't fit u8"), rh, dm, 0b00); - let (vector, interrupt_handle) = allocate_interrupt_vector().expect("xhcid: failed to allocate interrupt vector").expect("xhcid: no interrupt vectors left"); + let (vector, interrupt_handle) = allocate_single_interrupt_vector().expect("xhcid: failed to allocate interrupt vector").expect("xhcid: no interrupt vectors left"); let msg_data = x86_64_msix::message_data_edge_triggered(DeliveryMode::Fixed, vector); table_entry_pointer.addr_lo.write(addr); @@ -201,7 +174,12 @@ fn main() { table_entry_pointer.vec_ctl.writef(MsixTableEntry::VEC_CTL_MASK_BIT, false); (Some(interrupt_handle), InterruptMethod::MsiX(Mutex::new(info))) - } + }; + + pcid_handle.enable_feature(PciFeature::MsiX).expect("xhcid: failed to enable MSI-X"); + info!("Enabled MSI-X"); + + method } else if pci_config.func.legacy_interrupt_pin.is_some() { // legacy INTx# interrupt pins. (Some(File::open(format!("irq:{}", irq)).expect("xhcid: failed to open legacy IRQ file")), InterruptMethod::Intx)