dev/pci: MSI-X capability and APIC support for it

This commit is contained in:
Mark Poliakov 2023-12-09 15:27:09 +02:00
parent f166968e57
commit 506476e9c3
14 changed files with 525 additions and 45 deletions

View File

@ -33,10 +33,23 @@ pub struct IrqOptions {
pub trigger: IrqTrigger, pub trigger: IrqTrigger,
} }
#[derive(Clone, Copy, Debug)]
pub struct MsiInfo {
pub address: usize,
pub value: u32,
pub vector: usize,
}
pub trait InterruptTable { pub trait InterruptTable {
fn handler(&self, index: usize) -> Option<&'static dyn InterruptHandler>; fn handler(&self, index: usize) -> Option<&'static dyn InterruptHandler>;
} }
pub trait MessageInterruptController {
fn register_msi(&self, handler: &'static dyn MsiHandler) -> Result<MsiInfo, Error>;
fn handle_msi(&self, #[allow(unused)] vector: usize) {}
}
pub trait ExternalInterruptController { pub trait ExternalInterruptController {
type IrqNumber; type IrqNumber;
@ -80,6 +93,10 @@ pub trait InterruptHandler: Device {
fn handle_irq(&self) -> bool; fn handle_irq(&self) -> bool;
} }
pub trait MsiHandler: Device {
fn handle_msi(&self, vector: usize) -> bool;
}
pub struct FixedInterruptTable<const SIZE: usize> { pub struct FixedInterruptTable<const SIZE: usize> {
entries: [Option<&'static dyn InterruptHandler>; SIZE], entries: [Option<&'static dyn InterruptHandler>; SIZE],
} }

View File

@ -21,7 +21,10 @@ macro_rules! absolute_address {
use cfg_if::cfg_if; use cfg_if::cfg_if;
use device_api::{ use device_api::{
interrupt::{ExternalInterruptController, IpiDeliveryTarget, LocalInterruptController}, interrupt::{
ExternalInterruptController, IpiDeliveryTarget, LocalInterruptController,
MessageInterruptController,
},
timer::MonotonicTimestampProviderDevice, timer::MonotonicTimestampProviderDevice,
ResetDevice, ResetDevice,
}; };
@ -157,6 +160,14 @@ pub trait Architecture {
Err(Error::NotImplemented) Err(Error::NotImplemented)
} }
/// Adds a message-signalled interrupt (MSI/MSI-X) controller to the system
fn register_message_interrupt_controller(
&self,
intc: &'static dyn MessageInterruptController,
) -> Result<(), Error> {
Err(Error::NotImplemented)
}
/// Adds a monotonic timer to the system /// Adds a monotonic timer to the system
fn register_monotonic_timer( fn register_monotonic_timer(
&self, &self,
@ -185,6 +196,11 @@ pub trait Architecture {
unimplemented!() unimplemented!()
} }
/// Returns the MSI/MSI-X-capable interrupt controller
fn message_interrupt_controller(&'static self) -> &'static dyn MessageInterruptController {
unimplemented!()
}
/// Returns the monotonic timer /// Returns the monotonic timer
fn monotonic_timer(&'static self) -> &'static dyn MonotonicTimestampProviderDevice { fn monotonic_timer(&'static self) -> &'static dyn MonotonicTimestampProviderDevice {
unimplemented!() unimplemented!()

View File

@ -20,7 +20,7 @@ use crate::{
mem::{address::FromRaw, device::DeviceMemoryIo, PhysicalAddress}, mem::{address::FromRaw, device::DeviceMemoryIo, PhysicalAddress},
}; };
use super::{APIC_EXTERNAL_OFFSET, MAX_EXTERNAL_VECTORS}; use super::{APIC_EXTERNAL_OFFSET, POPULATED_EXTERNAL_VECTORS};
// IRQ 0 is timer, IRQ 1 reserved (for now?), +32 offset for exception entries // IRQ 0 is timer, IRQ 1 reserved (for now?), +32 offset for exception entries
const IO_APIC_VECTOR_OFFSET: u32 = 32 + APIC_EXTERNAL_OFFSET; const IO_APIC_VECTOR_OFFSET: u32 = 32 + APIC_EXTERNAL_OFFSET;
@ -64,7 +64,7 @@ pub struct IoApic {
inner: IrqSafeSpinlock<Inner>, inner: IrqSafeSpinlock<Inner>,
isa_redirections: [Option<IsaRedirection>; 16], isa_redirections: [Option<IsaRedirection>; 16],
table: IrqSafeSpinlock<FixedInterruptTable<{ MAX_EXTERNAL_VECTORS as usize }>>, table: IrqSafeSpinlock<FixedInterruptTable<{ POPULATED_EXTERNAL_VECTORS as usize }>>,
} }
impl Regs { impl Regs {

View File

@ -1,11 +1,16 @@
//! x86-64 Local APIC driver implementation //! x86-64 Local APIC driver implementation
use core::sync::atomic::Ordering; use core::sync::atomic::Ordering;
use abi::error::Error;
use alloc::vec::Vec;
use device_api::{ use device_api::{
interrupt::{IpiDeliveryTarget, LocalInterruptController}, interrupt::{
IpiDeliveryTarget, LocalInterruptController, MessageInterruptController, MsiHandler,
MsiInfo,
},
Device, Device,
}; };
use kernel_util::util::OneTimeInit; use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
use tock_registers::{ use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable}, interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs, register_bitfields, register_structs,
@ -14,10 +19,16 @@ use tock_registers::{
use crate::{ use crate::{
arch::{ arch::{
x86_64::{mem::table::L3, registers::MSR_IA32_APIC_BASE, smp::CPU_COUNT}, x86_64::{
apic::APIC_MSI_OFFSET, mem::table::L3, registers::MSR_IA32_APIC_BASE, smp::CPU_COUNT,
},
CpuMessage, CpuMessage,
}, },
mem::{address::FromRaw, device::DeviceMemoryIo, PhysicalAddress}, mem::{
address::{FromRaw, IntoRaw},
device::DeviceMemoryIo,
PhysicalAddress,
},
task::Cpu, task::Cpu,
}; };
@ -131,6 +142,7 @@ register_structs! {
/// Per-processor local APIC interface /// Per-processor local APIC interface
pub struct LocalApic { pub struct LocalApic {
regs: DeviceMemoryIo<'static, Regs>, regs: DeviceMemoryIo<'static, Regs>,
msi_vectors: IrqSafeSpinlock<Vec<&'static dyn MsiHandler>>,
} }
unsafe impl Send for LocalApic {} unsafe impl Send for LocalApic {}
@ -142,14 +154,51 @@ impl Device for LocalApic {
} }
} }
impl MessageInterruptController for LocalApic {
fn handle_msi(&self, vector: usize) {
// TODO this is ugly
let mut i = 0;
loop {
let table = self.msi_vectors.lock();
let Some(&handler) = table.get(i) else {
break;
};
drop(table);
if handler.handle_msi(vector) {
break;
}
i += 1;
}
}
fn register_msi(&self, handler: &'static dyn MsiHandler) -> Result<MsiInfo, Error> {
// TODO only 1 ISR vector allocated for MSIs
let vector = 0;
let mut table = self.msi_vectors.lock();
table.push(handler);
// TODO magic numbers
let apic_vector = 32 + APIC_MSI_OFFSET + vector;
let value = apic_vector;
let address = Self::base();
Ok(MsiInfo {
address: address.into_raw(),
value,
vector: vector as _,
})
}
}
impl LocalInterruptController for LocalApic { impl LocalInterruptController for LocalApic {
type IpiMessage = CpuMessage; type IpiMessage = CpuMessage;
fn send_ipi( fn send_ipi(&self, target: IpiDeliveryTarget, msg: Self::IpiMessage) -> Result<(), Error> {
&self,
target: device_api::interrupt::IpiDeliveryTarget,
msg: Self::IpiMessage,
) -> Result<(), abi::error::Error> {
while self.regs.ICR0.matches_all(ICR0::DeliveryStatus::SET) { while self.regs.ICR0.matches_all(ICR0::DeliveryStatus::SET) {
core::hint::spin_loop(); core::hint::spin_loop();
} }
@ -178,7 +227,7 @@ impl LocalInterruptController for LocalApic {
} }
} }
unsafe fn init_ap(&self) -> Result<(), abi::error::Error> { unsafe fn init_ap(&self) -> Result<(), Error> {
todo!() todo!()
} }
} }
@ -228,7 +277,10 @@ impl LocalApic {
LocalVectorEntry::Mask::Masked + LocalVectorEntry::Vector.val(APIC_LINT1_VECTOR + 32), LocalVectorEntry::Mask::Masked + LocalVectorEntry::Vector.val(APIC_LINT1_VECTOR + 32),
); );
Self { regs } Self {
regs,
msi_vectors: IrqSafeSpinlock::new(Vec::new()),
}
} }
/// Signals local APIC that we've handled the IRQ /// Signals local APIC that we've handled the IRQ

View File

@ -2,6 +2,8 @@
use core::arch::global_asm; use core::arch::global_asm;
use static_assertions::{const_assert, const_assert_eq};
use crate::{ use crate::{
arch::{x86_64::cpu::Cpu, Architecture}, arch::{x86_64::cpu::Cpu, Architecture},
task::thread::Thread, task::thread::Thread,
@ -29,8 +31,17 @@ pub const APIC_IPI_VECTOR: u32 = 0x03;
pub const APIC_SPURIOUS_VECTOR: u32 = 0xDF; pub const APIC_SPURIOUS_VECTOR: u32 = 0xDF;
/// Start of the I/O APIC IRQ range /// Start of the I/O APIC IRQ range
pub const APIC_EXTERNAL_OFFSET: u32 = 4; pub const APIC_EXTERNAL_OFFSET: u32 = 4;
/// Start of the MSI range
pub const APIC_MSI_OFFSET: u32 = APIC_EXTERNAL_OFFSET + MAX_EXTERNAL_VECTORS;
/// Maximum number of APIC vectors allocated for handling IRQs from I/O APIC /// Maximum number of APIC vectors allocated for handling IRQs from I/O APIC
pub const MAX_EXTERNAL_VECTORS: u32 = 16; pub const MAX_EXTERNAL_VECTORS: u32 = APIC_SPURIOUS_VECTOR - APIC_EXTERNAL_OFFSET - MAX_MSI_VECTORS;
/// Number of I/O APIC IRQ vectors that are actually populated
pub const POPULATED_EXTERNAL_VECTORS: u32 = 16;
/// Maximum number of APIC vectors allocated for handling MSIs
pub const MAX_MSI_VECTORS: u32 = 16;
const_assert!(POPULATED_EXTERNAL_VECTORS <= MAX_EXTERNAL_VECTORS);
const_assert_eq!(APIC_MSI_OFFSET + MAX_MSI_VECTORS, APIC_SPURIOUS_VECTOR);
/// Fills the IDT with interrupt vectors for this APIC /// Fills the IDT with interrupt vectors for this APIC
pub fn setup_vectors(idt: &mut [exception::Entry]) { pub fn setup_vectors(idt: &mut [exception::Entry]) {
@ -49,6 +60,10 @@ pub fn setup_vectors(idt: &mut [exception::Entry]) {
} }
unsafe extern "C" fn irq_handler(vector: usize, frame: *mut IrqFrame) { unsafe extern "C" fn irq_handler(vector: usize, frame: *mut IrqFrame) {
if vector >= POPULATED_EXTERNAL_VECTORS as _ {
todo!("Got a weird IRQ with vector {}", vector);
}
let cpu = Cpu::local(); let cpu = Cpu::local();
let frame = &mut *frame; let frame = &mut *frame;
@ -62,6 +77,24 @@ unsafe extern "C" fn irq_handler(vector: usize, frame: *mut IrqFrame) {
} }
} }
unsafe extern "C" fn msi_handler(vector: usize, frame: *mut IrqFrame) {
if vector != 0 {
todo!("Got a weird MSI with vector {}", vector);
}
let cpu = Cpu::local();
let frame = &mut *frame;
ARCHITECTURE
.message_interrupt_controller()
.handle_msi(vector);
cpu.local_apic().clear_interrupt();
if let Some(thread) = Thread::get_current() {
thread.handle_pending_signals(frame);
}
}
unsafe extern "C" fn local_timer_irq_handler(frame: *mut IrqFrame) { unsafe extern "C" fn local_timer_irq_handler(frame: *mut IrqFrame) {
let frame = &mut *frame; let frame = &mut *frame;
let cpu = Cpu::local(); let cpu = Cpu::local();
@ -87,7 +120,12 @@ global_asm!(
include_str!("vectors.S"), include_str!("vectors.S"),
local_timer_irq_handler = sym local_timer_irq_handler, local_timer_irq_handler = sym local_timer_irq_handler,
irq_handler = sym irq_handler, irq_handler = sym irq_handler,
msi_handler = sym msi_handler,
ipi_handler = sym ipi_handler, ipi_handler = sym ipi_handler,
dummy_irq_handler = sym dummy_irq_handler, dummy_irq_handler = sym dummy_irq_handler,
irq_vector_offset = const APIC_EXTERNAL_OFFSET,
irq_vector_count = const MAX_EXTERNAL_VECTORS,
msi_vector_offset = const APIC_MSI_OFFSET,
msi_vector_count = const MAX_MSI_VECTORS,
options(att_syntax) options(att_syntax)
); );

View File

@ -123,6 +123,56 @@ irq_vector_\n:
.endr .endr
.endm .endm
.macro MSI_VECTOR, n
msi_vector_\n:
// %rsp + 0: %rip
// %rsp + 8: %cs
IRQ_SWAPGS_IF_NEEDED 8
IRQ_SAVE_STATE
// Force correct segment registers
mov $0x10, %ax
mov %ax, %ss
mov %ax, %ds
mov %ax, %es
mov $\n, %rdi
mov %rbp, %rsi
call {msi_handler}
IRQ_RESTORE_STATE
IRQ_SWAPGS_IF_NEEDED 8
iretq
.endm
.macro MSI_VECTOR_ENTRY, n
.quad msi_vector_\n
.endm
.macro MSI_VECTORS, start, end
.set i, 0
.rept \end - \start
MSI_VECTOR %i
.set i, i+1
.endr
.endm
.macro MSI_VECTOR_ENTRIES, start, end
.set i, 0
.rept \end - \start
MSI_VECTOR_ENTRY %i
.set i, i+1
.endr
.endm
.macro FILL_EMPTY_SPACE, start, end
.set i, 0
.rept \end - \start
.quad dummy_vector
.endr
.endm
.section .text .section .text
local_timer_vector: local_timer_vector:
IRQ_SWAPGS_IF_NEEDED 8 IRQ_SWAPGS_IF_NEEDED 8
@ -149,7 +199,8 @@ dummy_vector:
call {dummy_irq_handler} call {dummy_irq_handler}
jmp . jmp .
IRQ_VECTORS 4, 255 IRQ_VECTORS {irq_vector_offset}, {irq_vector_offset} + {irq_vector_count}
MSI_VECTORS {msi_vector_offset}, {msi_vector_offset} + {msi_vector_count}
.section .rodata .section .rodata
// 224 vectors: 256 - 32 (exceptions) // 224 vectors: 256 - 32 (exceptions)
@ -163,8 +214,10 @@ __x86_64_apic_vectors:
.quad dummy_vector .quad dummy_vector
// IPI vector: 3 // IPI vector: 3
.quad ipi_vector .quad ipi_vector
// Regular IRQ vectors: 4..=222 // Regular IRQ vectors: 4..207
IRQ_VECTOR_ENTRIES 4, 223 IRQ_VECTOR_ENTRIES {irq_vector_offset}, {irq_vector_offset} + {irq_vector_count}
// MSI vectors: 207..223
MSI_VECTOR_ENTRIES {msi_vector_offset}, {msi_vector_offset} + {msi_vector_count}
// Spurious interrupt vector: 223 // Spurious interrupt vector: 223
.quad dummy_vector .quad dummy_vector
.size __x86_64_apic_vectors, . - __x86_64_apic_vectors .size __x86_64_apic_vectors, . - __x86_64_apic_vectors

View File

@ -5,8 +5,10 @@ use abi::error::Error;
use acpi_lib::{mcfg::Mcfg, AcpiTables, InterruptModel}; use acpi_lib::{mcfg::Mcfg, AcpiTables, InterruptModel};
use alloc::boxed::Box; use alloc::boxed::Box;
use device_api::{ use device_api::{
input::KeyboardProducer, interrupt::ExternalInterruptController, input::KeyboardProducer,
timer::MonotonicTimestampProviderDevice, Device, interrupt::{ExternalInterruptController, MessageInterruptController},
timer::MonotonicTimestampProviderDevice,
Device,
}; };
use git_version::git_version; use git_version::git_version;
use kernel_util::{sync::SpinFence, util::OneTimeInit}; use kernel_util::{sync::SpinFence, util::OneTimeInit};
@ -281,6 +283,10 @@ impl Architecture for X86_64 {
self.ioapic.get() self.ioapic.get()
} }
fn message_interrupt_controller(&'static self) -> &'static dyn MessageInterruptController {
Cpu::local().local_apic()
}
fn monotonic_timer(&'static self) -> &'static dyn MonotonicTimestampProviderDevice { fn monotonic_timer(&'static self) -> &'static dyn MonotonicTimestampProviderDevice {
self.timer.get() self.timer.get()
} }

View File

@ -0,0 +1,105 @@
//! PCI capability structures and queries
use abi::error::Error;
use device_api::interrupt::{MessageInterruptController, MsiHandler};
use crate::{
device::bus::pci::PciBaseAddress,
mem::{address::FromRaw, device::DeviceMemoryIoMut, PhysicalAddress},
};
use super::{PciCapability, PciCapabilityId, PciConfigurationSpace};
/// MSI-X capability query
pub struct MsiXCapability;
/// Represents an entry in MSI-X vector table
#[repr(C)]
pub struct MsiXEntry {
/// Address to which the value is written on interrupt
pub address: u64,
/// Value which is written to trigger an interrupt
pub data: u32,
/// Vector control word
pub control: u32,
}
/// MSI-X capability data structure
pub struct MsiXData<'s, S: PciConfigurationSpace + ?Sized + 's> {
space: &'s S,
offset: usize,
}
impl PciCapability for MsiXCapability {
const ID: PciCapabilityId = PciCapabilityId::MsiX;
type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a> = MsiXData<'a, S>;
fn data<'s, S: PciConfigurationSpace + ?Sized + 's>(
space: &'s S,
offset: usize,
) -> Self::CapabilityData<'s, S> {
MsiXData { space, offset }
}
}
impl<'s, S: PciConfigurationSpace + ?Sized + 's> MsiXData<'s, S> {
// TODO use pending bits as well
/// Maps and returns the vector table associated with the device's MSI-X capability
pub fn vector_table<'a>(&self) -> Result<DeviceMemoryIoMut<'a, [MsiXEntry]>, Error> {
let w0 = self.space.read_u16(self.offset + 2);
let dw1 = self.space.read_u32(self.offset + 4);
let table_size = (w0 as usize & 0x3FF) + 1;
let bir = dw1 as usize & 0x3;
let table_offset = dw1 as usize & !0x3;
let Some(base) = self.space.bar(bir) else {
return Err(Error::DoesNotExist);
};
let PciBaseAddress::Memory(base) = base else {
return Err(Error::InvalidOperation);
};
debugln!("MSI-X table address: {:#x}", base + table_offset);
unsafe {
DeviceMemoryIoMut::map_slice(PhysicalAddress::from_raw(base + table_offset), table_size)
}
}
/// Changes the global enable status for the device's MSI-X capability. If set, regular IRQs
/// are not generated.
pub fn set_enabled(&mut self, enabled: bool) {
let mut w0 = self.space.read_u32(self.offset);
if enabled {
w0 |= 1 << 31;
} else {
w0 &= !(1 << 31);
}
self.space.write_u32(self.offset, w0);
}
}
impl MsiXEntry {
/// If set, prevents the MSI-X interrupt from being delivered
pub fn set_masked(&mut self, masked: bool) {
if masked {
self.control |= 1;
} else {
self.control &= !1;
}
}
/// Registers the MSI-X vector with the interrupt controller and enables it
pub fn register<C: MessageInterruptController + ?Sized>(
&mut self,
ic: &C,
handler: &'static dyn MsiHandler,
) -> Result<(), Error> {
let info = ic.register_msi(handler)?;
self.address = info.address as _;
self.data = info.value as _;
self.set_masked(false);
Ok(())
}
}

View File

@ -8,6 +8,7 @@ use device_api::Device;
use kernel_util::sync::IrqSafeSpinlock; use kernel_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::error::Error; use yggdrasil_abi::error::Error;
pub mod capability;
mod space; mod space;
pub use space::{ pub use space::{
@ -33,6 +34,14 @@ bitflags! {
} }
} }
bitflags! {
/// Status register of the PCI configuration space
pub struct PciStatusRegister: u16 {
/// Read-only. If set, the configuration space has a pointer to the capabilities list.
const CAPABILITIES_LIST = 1 << 4;
}
}
/// Represents the address of a single object on a bus (or the bus itself) /// Represents the address of a single object on a bus (or the bus itself)
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PciAddress { pub struct PciAddress {
@ -55,6 +64,33 @@ pub enum PciBaseAddress {
Io(u16), Io(u16),
} }
/// Unique ID assigned to PCI capability structures
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
#[repr(u8)]
pub enum PciCapabilityId {
/// MSI (32-bit or 64-bit)
Msi = 0x05,
/// MSI-X
MsiX = 0x11,
/// Unknown capability missing from this list
Unknown,
}
/// Interface used for querying PCI capabilities
pub trait PciCapability {
/// Capability ID
const ID: PciCapabilityId;
/// Wrapper for accessing the capability data structure
type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a>;
/// Constructs an access wrapper for this capability with given offset
fn data<'s, S: PciConfigurationSpace + ?Sized + 's>(
space: &'s S,
offset: usize,
) -> Self::CapabilityData<'s, S>;
}
/// Describes a PCI device /// Describes a PCI device
#[derive(Debug)] #[derive(Debug)]
pub struct PciDeviceInfo { pub struct PciDeviceInfo {

View File

@ -1,4 +1,6 @@
use super::{PciAddress, PciBaseAddress, PciEcam}; use crate::device::bus::pci::PciStatusRegister;
use super::{PciAddress, PciBaseAddress, PciCapability, PciCapabilityId, PciEcam};
pub(super) mod ecam; pub(super) mod ecam;
@ -72,6 +74,30 @@ pub enum PciConfigSpace {
Ecam(PciEcam), Ecam(PciEcam),
} }
pub struct CapabilityIterator<'s, S: PciConfigurationSpace + ?Sized> {
space: &'s S,
current: Option<usize>,
}
impl<'s, S: PciConfigurationSpace + ?Sized> Iterator for CapabilityIterator<'s, S> {
type Item = (PciCapabilityId, usize);
fn next(&mut self) -> Option<Self::Item> {
let offset = self.current? & !0x3;
let id = unsafe { core::mem::transmute(self.space.read_u8(offset)) };
let next_pointer = self.space.read_u8(offset + 1);
self.current = if next_pointer != 0 {
Some(next_pointer as usize)
} else {
None
};
Some((id, offset))
}
}
/// Interface for accessing the configuration space of a device /// Interface for accessing the configuration space of a device
pub trait PciConfigurationSpace { pub trait PciConfigurationSpace {
/// Reads a 32-bit value from the device configuration space. /// Reads a 32-bit value from the device configuration space.
@ -120,7 +146,7 @@ pub trait PciConfigurationSpace {
} }
/// Writes a byte to the device configuration space /// Writes a byte to the device configuration space
fn write_u8(&self, offset: usize, value: u16) { fn write_u8(&self, _offset: usize, _value: u16) {
todo!() todo!()
} }
@ -185,6 +211,14 @@ pub trait PciConfigurationSpace {
"#] "#]
secondary_bus secondary_bus
); );
pci_config_field!(
0x34 => u8,
#[doc =
r"Returns the offset within the configuration space where the Capabilities List
is located. Only valid if the corresponding Status Register bit is set"
]
capability_pointer
);
/// Returns the value of the Base Address Register with given index. /// Returns the value of the Base Address Register with given index.
/// ///
@ -242,4 +276,38 @@ pub trait PciConfigurationSpace {
} }
} }
} }
/// Returns an iterator over the PCI capabilities
fn capability_iter(&self) -> CapabilityIterator<Self> {
let status = PciStatusRegister::from_bits_retain(self.status());
let current = if status.contains(PciStatusRegister::CAPABILITIES_LIST) {
let ptr = self.capability_pointer() as usize;
if ptr != 0 {
Some(self.capability_pointer() as usize)
} else {
None
}
} else {
// Return an empty iterator
None
};
CapabilityIterator {
space: self,
current,
}
}
/// Locates a capability within this configuration space
fn capability<'s, C: PciCapability>(&'s self) -> Option<C::CapabilityData<'s, Self>> {
self.capability_iter().find_map(|(id, offset)| {
if id == C::ID {
Some(C::data(self, offset))
} else {
None
}
})
}
} }

View File

@ -1,3 +1,5 @@
#![allow(unused)]
use core::fmt::{self, Write}; use core::fmt::{self, Write};
use crate::{ use crate::{
@ -47,6 +49,7 @@ pub struct IdentifyControllerRequest {
pub struct CreateIoCompletionQueue { pub struct CreateIoCompletionQueue {
pub id: u32, pub id: u32,
pub size: usize, pub size: usize,
pub vector: u32,
pub data: PhysicalAddress, pub data: PhysicalAddress,
} }
@ -110,9 +113,7 @@ impl Command for CreateIoCompletionQueue {
sqe.command.set_opcode(0x05); sqe.command.set_opcode(0x05);
sqe.data_pointer[1] = PhysicalRegionPage::with_addr(self.data.into_raw()); sqe.data_pointer[1] = PhysicalRegionPage::with_addr(self.data.into_raw());
sqe.command_specific[0] = ((self.size as u32 - 1) << 16) | self.id; sqe.command_specific[0] = ((self.size as u32 - 1) << 16) | self.id;
sqe.command_specific[1] = 1; sqe.command_specific[1] = (self.vector << 16) | 3;
// TODO ENABLE IRQS HERE
sqe.command_specific[2] = 0;
} }
} }

View File

@ -3,7 +3,7 @@ use core::{mem::size_of, time::Duration};
use abi::error::Error; use abi::error::Error;
use alloc::vec::Vec; use alloc::vec::Vec;
use device_api::Device; use device_api::{interrupt::MsiHandler, Device};
use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit}; use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
use tock_registers::{ use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable}, interfaces::{ReadWriteable, Readable, Writeable},
@ -12,8 +12,11 @@ use tock_registers::{
}; };
use crate::{ use crate::{
arch::{Architecture, ARCHITECTURE},
device::{ device::{
bus::pci::{PciBaseAddress, PciCommandRegister, PciConfigurationSpace}, bus::pci::{
capability::MsiXCapability, PciBaseAddress, PciCommandRegister, PciConfigurationSpace,
},
nvme::{ nvme::{
command::IdentifyControllerRequest, command::IdentifyControllerRequest,
queue::{CompletionQueueEntry, SubmissionQueueEntry}, queue::{CompletionQueueEntry, SubmissionQueueEntry},
@ -21,7 +24,7 @@ use crate::{
}, },
mem::{ mem::{
address::{FromRaw, IntoRaw}, address::{FromRaw, IntoRaw},
device::DeviceMemoryIo, device::{DeviceMemoryIo, DeviceMemoryIoMut},
PhysicalAddress, PhysicalAddress,
}, },
task::runtime, task::runtime,
@ -33,7 +36,7 @@ use self::{
queue::QueuePair, queue::QueuePair,
}; };
use super::bus::pci::{FromPciBus, PciDeviceInfo}; use super::bus::pci::{capability::MsiXEntry, FromPciBus, PciDeviceInfo};
mod command; mod command;
mod error; mod error;
@ -109,6 +112,7 @@ pub struct NvmeController {
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>, regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
admin_q: OneTimeInit<QueuePair<'static>>, admin_q: OneTimeInit<QueuePair<'static>>,
ioqs: OneTimeInit<Vec<QueuePair<'static>>>, ioqs: OneTimeInit<Vec<QueuePair<'static>>>,
vector_table: IrqSafeSpinlock<DeviceMemoryIoMut<'static, [MsiXEntry]>>,
doorbell_shift: usize, doorbell_shift: usize,
} }
@ -123,9 +127,11 @@ impl Regs {
impl NvmeController { impl NvmeController {
async fn late_init(&'static self) -> Result<(), NvmeError> { async fn late_init(&'static self) -> Result<(), NvmeError> {
runtime::spawn(self.poll_task()).expect("Couldn't spawn NVMe poll task"); // runtime::spawn(self.poll_task()).expect("Couldn't spawn NVMe poll task");
let admin_q = self.admin_q.get(); let admin_q = self.admin_q.get();
infoln!("SETUP");
// Request a CQ/SQ pair for I/O // Request a CQ/SQ pair for I/O
admin_q admin_q
.request_no_data(SetFeatureRequest::NumberOfQueues(1, 1)) .request_no_data(SetFeatureRequest::NumberOfQueues(1, 1))
@ -133,7 +139,8 @@ impl NvmeController {
// Allocate the queue // Allocate the queue
let (sq_doorbell, cq_doorbell) = unsafe { self.doorbell_pair(1) }; let (sq_doorbell, cq_doorbell) = unsafe { self.doorbell_pair(1) };
let io_q = QueuePair::new(32, sq_doorbell, cq_doorbell).map_err(NvmeError::MemoryError)?; let io_q =
QueuePair::new(0, 32, sq_doorbell, cq_doorbell).map_err(NvmeError::MemoryError)?;
// Identify the controller // Identify the controller
let identify = admin_q let identify = admin_q
@ -145,6 +152,7 @@ impl NvmeController {
.request_no_data(CreateIoCompletionQueue { .request_no_data(CreateIoCompletionQueue {
id: 1, id: 1,
size: 32, size: 32,
vector: 0,
data: io_q.cq_physical_pointer(), data: io_q.cq_physical_pointer(),
}) })
.await?; .await?;
@ -160,14 +168,6 @@ impl NvmeController {
loop {} loop {}
} }
// TODO MSI(-X) or IRQ (ACPI currently broken) support for PCIe-based NVMe
async fn poll_task(&'static self) {
loop {
self.admin_q.get().process_completions();
runtime::sleep(Duration::from_millis(100)).await;
}
}
unsafe fn doorbell_pair(&self, idx: usize) -> (*mut u32, *mut u32) { unsafe fn doorbell_pair(&self, idx: usize) -> (*mut u32, *mut u32) {
let regs = self.regs.lock(); let regs = self.regs.lock();
let sq_ptr = regs.doorbell_ptr(self.doorbell_shift, false, idx); let sq_ptr = regs.doorbell_ptr(self.doorbell_shift, false, idx);
@ -176,12 +176,18 @@ impl NvmeController {
} }
} }
impl MsiHandler for NvmeController {
fn handle_msi(&self, vector: usize) -> bool {
debugln!("handle_msi {}", vector);
self.admin_q.get().process_completions() != 0
}
}
impl Device for NvmeController { impl Device for NvmeController {
unsafe fn init(&'static self) -> Result<(), Error> { unsafe fn init(&'static self) -> Result<(), Error> {
let regs = self.regs.lock(); let regs = self.regs.lock();
let min_page_size = 1usize << (12 + regs.CAP.read(CAP::MPSMIN)); let min_page_size = 1usize << (12 + regs.CAP.read(CAP::MPSMIN));
let max_page_size = 1usize << (12 + regs.CAP.read(CAP::MPSMAX));
if min_page_size > 4096 { if min_page_size > 4096 {
panic!(); panic!();
@ -205,8 +211,13 @@ impl Device for NvmeController {
// Setup the admin queue (index 0) // Setup the admin queue (index 0)
let admin_sq_doorbell = unsafe { regs.doorbell_ptr(self.doorbell_shift, false, 0) }; let admin_sq_doorbell = unsafe { regs.doorbell_ptr(self.doorbell_shift, false, 0) };
let admin_cq_doorbell = unsafe { regs.doorbell_ptr(self.doorbell_shift, true, 0) }; let admin_cq_doorbell = unsafe { regs.doorbell_ptr(self.doorbell_shift, true, 0) };
let admin_q = let admin_q = QueuePair::new(
QueuePair::new(queue_slots as usize, admin_sq_doorbell, admin_cq_doorbell).unwrap(); 0,
queue_slots as usize,
admin_sq_doorbell,
admin_cq_doorbell,
)
.unwrap();
regs.AQA regs.AQA
.modify(AQA::ASQS.val(queue_slots as u32 - 1) + AQA::ACQS.val(queue_slots as u32 - 1)); .modify(AQA::ASQS.val(queue_slots as u32 - 1) + AQA::ACQS.val(queue_slots as u32 - 1));
@ -239,6 +250,16 @@ impl Device for NvmeController {
self.admin_q.init(admin_q); self.admin_q.init(admin_q);
// Register the IRQs (TODO: use multiple)
{
let mut vt = self.vector_table.lock();
// Register vector 0
vt[0]
.register(ARCHITECTURE.message_interrupt_controller(), self)
.unwrap();
}
// Schedule late_init task // Schedule late_init task
runtime::spawn(self.late_init())?; runtime::spawn(self.late_init())?;
@ -256,6 +277,15 @@ impl FromPciBus for NvmeController {
panic!(); panic!();
}; };
// TODO also support MSI
let mut msix = info.config_space.capability::<MsiXCapability>().unwrap();
let mut vt = msix.vector_table()?;
for vector in vt.iter_mut() {
vector.set_masked(true);
}
msix.set_enabled(true);
let mut cmd = PciCommandRegister::from_bits_retain(info.config_space.command()); let mut cmd = PciCommandRegister::from_bits_retain(info.config_space.command());
cmd &= !(PciCommandRegister::DISABLE_INTERRUPTS | PciCommandRegister::ENABLE_IO); cmd &= !(PciCommandRegister::DISABLE_INTERRUPTS | PciCommandRegister::ENABLE_IO);
cmd |= PciCommandRegister::ENABLE_MEMORY | PciCommandRegister::BUS_MASTER; cmd |= PciCommandRegister::ENABLE_MEMORY | PciCommandRegister::BUS_MASTER;
@ -272,6 +302,7 @@ impl FromPciBus for NvmeController {
regs: IrqSafeSpinlock::new(regs), regs: IrqSafeSpinlock::new(regs),
admin_q: OneTimeInit::new(), admin_q: OneTimeInit::new(),
ioqs: OneTimeInit::new(), ioqs: OneTimeInit::new(),
vector_table: IrqSafeSpinlock::new(vt),
doorbell_shift, doorbell_shift,
}) })
} }

View File

@ -1,6 +1,5 @@
use core::{ use core::{
mem::size_of, mem::size_of,
ops::DerefMut,
pin::Pin, pin::Pin,
ptr::null_mut, ptr::null_mut,
task::{Context, Poll}, task::{Context, Poll},
@ -94,6 +93,8 @@ pub struct QueuePair<'a> {
base: PhysicalAddress, base: PhysicalAddress,
page_count: usize, page_count: usize,
vector: usize,
sq_base: PhysicalAddress, sq_base: PhysicalAddress,
cq_base: PhysicalAddress, cq_base: PhysicalAddress,
@ -246,6 +247,7 @@ impl<'a, T> Queue<'a, T> {
impl<'a> QueuePair<'a> { impl<'a> QueuePair<'a> {
pub fn new( pub fn new(
vector: usize,
capacity: usize, capacity: usize,
sq_doorbell: *mut u32, sq_doorbell: *mut u32,
cq_doorbell: *mut u32, cq_doorbell: *mut u32,
@ -279,6 +281,7 @@ impl<'a> QueuePair<'a> {
Ok(Self { Ok(Self {
completion_notify: QueueWaker::new(), completion_notify: QueueWaker::new(),
vector,
base, base,
page_count, page_count,
sq_base, sq_base,

View File

@ -1,12 +1,19 @@
//! Facilities for mapping devices to virtual address space //! Facilities for mapping devices to virtual address space
use core::{mem::size_of, ops::Deref}; use core::{
alloc::Layout,
mem::size_of,
ops::{Deref, DerefMut},
};
use abi::error::Error; use abi::error::Error;
use alloc::sync::Arc; use alloc::sync::Arc;
use crate::arch::{Architecture, ARCHITECTURE}; use crate::arch::{Architecture, ARCHITECTURE};
use super::PhysicalAddress; use super::{
address::{AsPhysicalAddress, FromRaw},
PhysicalAddress,
};
/// Describes a single device memory mapping /// Describes a single device memory mapping
#[derive(Debug)] #[derive(Debug)]
@ -37,6 +44,14 @@ pub struct DeviceMemoryIo<'a, T: ?Sized> {
value: &'a T, value: &'a T,
} }
/// Describes a single typed and mutable device memory mapping
#[derive(Debug)]
pub struct DeviceMemoryIoMut<'a, T: ?Sized> {
#[allow(unused)]
inner: RawDeviceMemoryMapping,
value: &'a mut T,
}
impl RawDeviceMemoryMapping { impl RawDeviceMemoryMapping {
/// Maps a region of physical memory as device memory of given size. /// Maps a region of physical memory as device memory of given size.
/// ///
@ -130,3 +145,42 @@ impl<'a, T: ?Sized> Deref for DeviceMemoryIo<'a, T> {
self.value self.value
} }
} }
impl<T: ?Sized> AsPhysicalAddress for DeviceMemoryIo<'_, T> {
unsafe fn as_physical_address(&self) -> PhysicalAddress {
PhysicalAddress::from_raw(self.inner.base_address)
}
}
impl<'a, T: Sized> DeviceMemoryIoMut<'a, T> {
/// Maps a physical address as device memory to a slice `[T; len]`
///
/// # Safety
///
/// The caller must ensure the address actually points to a value of type `T`, as well as
/// proper access synchronization. The caller must also ensure the `len` is valid.
pub unsafe fn map_slice(
base: PhysicalAddress,
len: usize,
) -> Result<DeviceMemoryIoMut<'a, [T]>, Error> {
let layout = Layout::array::<T>(len).unwrap();
let inner = RawDeviceMemoryMapping::map(base, layout.size())?;
let value = core::slice::from_raw_parts_mut(inner.address as *mut T, len);
Ok(DeviceMemoryIoMut { inner, value })
}
}
impl<'a, T: ?Sized> Deref for DeviceMemoryIoMut<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a, T: ?Sized> DerefMut for DeviceMemoryIoMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value
}
}