Various fixes.

This commit is contained in:
4lDO2
2020-04-21 15:42:29 +02:00
parent afe9c88f37
commit 4f62888bb4
4 changed files with 183 additions and 59 deletions
+137
View File
@@ -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<u32> {
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::<usize>());
})
}
/// 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<Option<(u8, Vec<File>)>> {
if count == 0 { return Ok(None) }
let available_irqs = fs::read_dir("irq:")?;
let mut available_irq_numbers = available_irqs.filter_map(|entry| -> Option<io::Result<_>> {
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-<APIC
// ID>/<IRQ>`, thus no error but just None
match path_str.parse::<u8>() {
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<Option<(u8, Vec<File>)>> {
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<Option<(u8, File)>> {
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())))
}
@@ -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<T, E = PcidClientHandleError> = std::result::Result<T, E>;
// 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)),
}
}
}
+1 -1
View File
@@ -195,7 +195,7 @@ impl State {
fn handle_parsed_header(state: Arc<State>, 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} {:?}",
+35 -57
View File
@@ -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<u32> {
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::<usize>());
})
}
/// Allocate an interrupt vector, located at the BSP's IDT.
fn allocate_interrupt_vector() -> io::Result<Option<(u8, File)>> {
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::<u8>() {
// 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<Xhci>, 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)