Files
RedBear-OS/pcid/src/pci/msi.rs
T
2024-06-13 15:06:49 +02:00

445 lines
18 KiB
Rust

use std::fmt;
use super::bar::PciBar;
pub use super::cap::{MsiCapability, MsixCapability};
use pci_types::capability::PciCapabilityAddress;
use pci_types::{ConfigRegionAccess, PciAddress};
use serde::{Deserialize, Serialize};
use syscall::{Io, Mmio};
/// The address and data to use for MSI and MSI-X.
///
/// For MSI using this only works when you need a single interrupt vector.
/// For MSI-X you can have a single [MsiEntry] for each interrupt vector.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MsiAddrAndData {
pub(crate) addr: u64,
pub(crate) data: u32,
}
impl MsiAddrAndData {
pub fn new(addr: u64, data: u32) -> Self {
MsiAddrAndData { addr, data }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MsiInfo {
pub log2_multiple_message_capable: u8,
pub is_64bit: bool,
pub has_per_vector_masking: bool,
}
impl MsiCapability {
const MC_PVT_CAPABLE_BIT: u16 = 1 << 8;
const MC_64_BIT_ADDR_BIT: u16 = 1 << 7;
const MC_MULTI_MESSAGE_MASK: u16 = 0x000E;
const MC_MULTI_MESSAGE_SHIFT: u8 = 1;
const MC_MULTI_MESSAGE_ENABLE_MASK: u16 = 0x0070;
const MC_MULTI_MESSAGE_ENABLE_SHIFT: u8 = 4;
const MC_MSI_ENABLED_BIT: u16 = 1;
pub(crate) unsafe fn parse(addr: PciCapabilityAddress, access: &dyn ConfigRegionAccess) -> Self {
let dword = access.read(addr.address, addr.offset);
let message_control = (dword >> 16) as u16;
if message_control & Self::MC_PVT_CAPABLE_BIT != 0 {
if message_control & Self::MC_64_BIT_ADDR_BIT != 0 {
Self::_64BitAddressWithPvm {
cap_offset: addr.offset,
message_control: dword,
message_address_lo: access.read(addr.address, addr.offset + 4),
message_address_hi: access.read(addr.address, addr.offset + 8),
message_data: access.read(addr.address, addr.offset + 12),
mask_bits: access.read(addr.address, addr.offset + 16),
pending_bits: access.read(addr.address, addr.offset + 20),
}
} else {
Self::_32BitAddressWithPvm {
cap_offset: addr.offset,
message_control: dword,
message_address: access.read(addr.address, addr.offset + 4),
message_data: access.read(addr.address, addr.offset + 8),
mask_bits: access.read(addr.address, addr.offset + 12),
pending_bits: access.read(addr.address, addr.offset + 16),
}
}
} else {
if message_control & Self::MC_64_BIT_ADDR_BIT != 0 {
Self::_64BitAddress {
cap_offset: addr.offset,
message_control: dword,
message_address_lo: access.read(addr.address, addr.offset + 4),
message_address_hi: access.read(addr.address, addr.offset + 8),
message_data: access.read(addr.address, addr.offset + 12) as u16,
}
} else {
Self::_32BitAddress {
cap_offset: addr.offset,
message_control: dword,
message_address: access.read(addr.address, addr.offset + 4),
message_data: access.read(addr.address, addr.offset + 8) as u16,
}
}
}
}
fn cap_offset(&self) -> u16 {
match *self {
MsiCapability::_32BitAddress { cap_offset, .. }
| MsiCapability::_64BitAddress { cap_offset, .. }
| MsiCapability::_32BitAddressWithPvm { cap_offset, .. }
| MsiCapability::_64BitAddressWithPvm { cap_offset, .. } => u16::from(cap_offset),
}
}
fn message_control_raw(&self) -> u32 {
match self {
Self::_32BitAddress { message_control, .. } | Self::_64BitAddress { message_control, .. } | Self::_32BitAddressWithPvm { message_control, .. } | Self::_64BitAddressWithPvm { message_control, .. } => *message_control,
}
}
fn message_control(&self) -> u16 {
(self.message_control_raw() >> 16) as u16
}
pub(crate) fn set_message_control(&mut self, value: u16) {
let mut new_message_control = self.message_control_raw();
new_message_control &= 0x0000_FFFF;
new_message_control |= u32::from(value) << 16;
match self {
Self::_32BitAddress { ref mut message_control, .. }
| Self::_64BitAddress { ref mut message_control, .. }
| Self::_32BitAddressWithPvm { ref mut message_control, .. }
| Self::_64BitAddressWithPvm { ref mut message_control, .. } => *message_control = new_message_control,
}
}
pub(crate) unsafe fn write_message_control(&self, addr: PciAddress, access: &dyn ConfigRegionAccess) {
access.write(addr, self.cap_offset(), self.message_control_raw());
}
pub(crate) fn is_pvt_capable(&self) -> bool {
self.message_control() & Self::MC_PVT_CAPABLE_BIT != 0
}
pub(crate) fn has_64_bit_addr(&self) -> bool {
self.message_control() & Self::MC_64_BIT_ADDR_BIT != 0
}
pub(crate) fn set_enabled(&mut self, enabled: bool) {
let mut new_message_control = self.message_control() & (!Self::MC_MSI_ENABLED_BIT);
new_message_control |= u16::from(enabled);
self.set_message_control(new_message_control);
}
pub(crate) fn multi_message_capable(&self) -> u8 {
((self.message_control() & Self::MC_MULTI_MESSAGE_MASK) >> Self::MC_MULTI_MESSAGE_SHIFT) as u8
}
pub(crate) fn multi_message_enable(&self) -> u8 {
((self.message_control() & Self::MC_MULTI_MESSAGE_ENABLE_MASK) >> Self::MC_MULTI_MESSAGE_ENABLE_SHIFT) as u8
}
pub(crate) fn set_multi_message_enable(&mut self, log_mme: u8) {
let mut new_message_control = self.message_control() & (!Self::MC_MULTI_MESSAGE_ENABLE_MASK);
new_message_control |= u16::from(log_mme) << Self::MC_MULTI_MESSAGE_ENABLE_SHIFT;
self.set_message_control(new_message_control);
}
fn message_address(&self) -> u32 {
match self {
&Self::_32BitAddress { message_address, .. } | &Self::_32BitAddressWithPvm { message_address, .. } => message_address,
&Self::_64BitAddress { message_address_lo, .. } | &Self::_64BitAddressWithPvm { message_address_lo, .. } => message_address_lo,
}
}
fn message_upper_address(&self) -> Option<u32> {
match self {
&Self::_64BitAddress { message_address_hi, .. } | &Self::_64BitAddressWithPvm { message_address_hi, .. } => Some(message_address_hi),
&Self::_32BitAddress { .. } | &Self::_32BitAddressWithPvm { .. } => None,
}
}
pub(crate) fn set_message_address(&mut self, message_address: u32) {
assert_eq!(message_address & 0xFFFF_FFFC, message_address, "unaligned message address (this should already be validated)");
match self {
&mut Self::_32BitAddress { message_address: ref mut addr, .. } | &mut Self::_32BitAddressWithPvm { message_address: ref mut addr, .. } => *addr = message_address,
&mut Self::_64BitAddress { message_address_lo: ref mut addr, .. } | &mut Self::_64BitAddressWithPvm { message_address_lo: ref mut addr, .. } => *addr = message_address,
}
}
pub(crate) fn set_message_upper_address(&mut self, message_upper_address: u32) -> Option<()> {
match self {
&mut Self::_64BitAddress { ref mut message_address_hi, .. } | &mut Self::_64BitAddressWithPvm { ref mut message_address_hi, .. } => *message_address_hi = message_upper_address,
&mut Self::_32BitAddress { .. } | &mut Self::_32BitAddressWithPvm { .. } => return None,
}
Some(())
}
pub(crate) fn set_message_data(&mut self, value: u16) {
match self {
&mut Self::_32BitAddress { ref mut message_data, .. } | &mut Self::_64BitAddress { ref mut message_data, .. } => *message_data = value,
&mut Self::_32BitAddressWithPvm { ref mut message_data, .. } | &mut Self::_64BitAddressWithPvm { ref mut message_data, .. } => {
*message_data &= 0xFFFF_0000;
*message_data |= u32::from(value);
}
}
}
pub(crate) fn set_mask_bits(&mut self, mask_bits: u32) -> Option<()> {
match self {
&mut Self::_32BitAddressWithPvm { mask_bits: ref mut bits, .. } | &mut Self::_64BitAddressWithPvm { mask_bits: ref mut bits, .. } => *bits = mask_bits,
&mut Self::_32BitAddress { .. } | &mut Self::_64BitAddress { .. } => return None,
}
Some(())
}
unsafe fn write_message_address(&self, addr: PciAddress, access: &dyn ConfigRegionAccess) {
access.write(addr, self.cap_offset() + 4, self.message_address())
}
unsafe fn write_message_upper_address(&self, addr: PciAddress, access: &dyn ConfigRegionAccess) -> Option<()> {
let value = self.message_upper_address()?;
access.write(addr, self.cap_offset() + 8, value);
Some(())
}
unsafe fn write_message_data(&self, addr: PciAddress, access: &dyn ConfigRegionAccess) {
match self {
&Self::_32BitAddress { cap_offset, message_data, .. } => access.write(addr, u16::from(cap_offset + 8), message_data.into()),
&Self::_32BitAddressWithPvm { cap_offset, message_data, .. } => access.write(addr, u16::from(cap_offset + 8), message_data),
&Self::_64BitAddress { cap_offset, message_data, .. } => access.write(addr, u16::from(cap_offset + 12), message_data.into()),
&Self::_64BitAddressWithPvm { cap_offset, message_data, .. } => access.write(addr, u16::from(cap_offset + 12), message_data),
}
}
unsafe fn write_mask_bits(&self, addr: PciAddress, access: &dyn ConfigRegionAccess) -> Option<()> {
match self {
&Self::_32BitAddressWithPvm { cap_offset, mask_bits, .. } => access.write(addr, u16::from(cap_offset + 12), mask_bits),
&Self::_64BitAddressWithPvm { cap_offset, mask_bits, .. } => access.write(addr, u16::from(cap_offset + 16), mask_bits),
&Self::_32BitAddress { .. } | &Self::_64BitAddress { .. } => return None,
}
Some(())
}
pub(crate) unsafe fn write_all(&self, addr: PciAddress, access: &dyn ConfigRegionAccess) {
self.write_message_control(addr, access);
self.write_message_address(addr, access);
self.write_message_upper_address(addr, access);
self.write_message_data(addr, access);
self.write_mask_bits(addr, access);
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MsixInfo {
pub table_bar: u8,
pub table_offset: u32,
pub table_size: u16,
pub pba_bar: u8,
pub pba_offset: u32,
}
impl MsixInfo {
pub fn validate(&self, bars: [PciBar; 6]) {
if self.table_bar > 5 {
panic!("MSI-X Table BIR contained a reserved enum value: {}", self.table_bar);
}
if self.pba_bar > 5 {
panic!("MSI-X PBA BIR contained a reserved enum value: {}", self.pba_bar);
}
let table_size = self.table_size;
let table_offset = self.table_offset as usize;
let table_min_length = table_size * 16;
let pba_offset = self.pba_offset as usize;
let pba_min_length = table_size.div_ceil(8);
let (_, table_bar_size) = bars[self.table_bar as usize].expect_mem();
let (_, pba_bar_size) = bars[self.pba_bar as usize].expect_mem();
// Ensure that the table and PBA are within the BAR.
if !(0..table_bar_size as u64).contains(&(table_offset as u64 + table_min_length as u64)) {
panic!(
"Table {:#x}:{:#x} outside of BAR with length {:#x}",
table_offset,
table_offset + table_min_length as usize,
table_bar_size
);
}
if !(0..pba_bar_size as u64).contains(&(pba_offset as u64 + pba_min_length as u64)) {
panic!(
"PBA {:#x}:{:#x} outside of BAR with length {:#x}",
pba_offset,
pba_offset + pba_min_length as usize,
pba_bar_size
);
}
}
}
impl MsixCapability {
const MC_MSIX_ENABLED_BIT: u16 = 1 << 15;
const MC_MSIX_ENABLED_SHIFT: u8 = 15;
const MC_FUNCTION_MASK_BIT: u16 = 1 << 14;
const MC_FUNCTION_MASK_SHIFT: u8 = 14;
const MC_TABLE_SIZE_MASK: u16 = 0x03FF;
/// The Message Control field, containing the enabled and function mask bits, as well as the
/// table size.
const fn message_control(&self) -> u16 {
(self.a >> 16) as u16
}
pub(crate) fn set_message_control(&mut self, message_control: u16) {
self.a &= 0x0000_FFFF;
self.a |= u32::from(message_control) << 16;
}
/// Returns the MSI-X table size.
pub(crate) const fn table_size(&self) -> u16 {
(self.message_control() & Self::MC_TABLE_SIZE_MASK) + 1
}
pub(crate) fn set_msix_enabled(&mut self, enabled: bool) {
let mut new_message_control = self.message_control();
new_message_control &= !(Self::MC_MSIX_ENABLED_BIT);
new_message_control |= u16::from(enabled) << Self::MC_MSIX_ENABLED_SHIFT;
self.set_message_control(new_message_control);
}
pub(crate) fn set_function_mask(&mut self, function_mask: bool) {
let mut new_message_control = self.message_control();
new_message_control &= !(Self::MC_FUNCTION_MASK_BIT);
new_message_control |= u16::from(function_mask) << Self::MC_FUNCTION_MASK_SHIFT;
self.set_message_control(new_message_control);
}
const TABLE_OFFSET_MASK: u32 = 0xFFFF_FFF8;
const TABLE_BIR_MASK: u32 = 0x0000_0007;
/// The table offset is guaranteed to be QWORD aligned (8 bytes).
pub(crate) const fn table_offset(&self) -> u32 {
self.b & Self::TABLE_OFFSET_MASK
}
/// The table BIR, which is used to map the offset to a memory location.
pub(crate) const fn table_bir(&self) -> u8 {
(self.b & Self::TABLE_BIR_MASK) as u8
}
const PBA_OFFSET_MASK: u32 = 0xFFFF_FFF8;
const PBA_BIR_MASK: u32 = 0x0000_0007;
/// The Pending Bit Array offset is guaranteed to be QWORD aligned (8 bytes).
pub(crate) const fn pba_offset(&self) -> u32 {
self.c & Self::PBA_OFFSET_MASK
}
/// The Pending Bit Array BIR, which is used to map the offset to a memory location.
pub(crate) const fn pba_bir(&self) -> u8 {
(self.c & Self::PBA_BIR_MASK) as u8
}
/// Write the first DWORD into configuration space (containing the partially modifiable Message
/// Control field).
pub(crate) unsafe fn write_a(&self, addr: PciAddress, access: &dyn ConfigRegionAccess) {
access.write(addr, u16::from(self.cap_offset), self.a)
}
}
#[repr(packed)]
pub struct MsixTableEntry {
pub addr_lo: Mmio<u32>,
pub addr_hi: Mmio<u32>,
pub msg_data: Mmio<u32>,
pub vec_ctl: Mmio<u32>,
}
#[cfg(target_arch = "x86_64")]
pub mod x86_64 {
#[repr(u8)]
pub enum TriggerMode {
Edge = 0,
Level = 1,
}
#[repr(u8)]
pub enum LevelTriggerMode {
Deassert = 0,
Assert = 1,
}
#[repr(u8)]
pub enum DeliveryMode {
Fixed = 0b000,
LowestPriority = 0b001,
Smi = 0b010,
// 0b011 is reserved
Nmi = 0b100,
Init = 0b101,
// 0b110 is reserved
ExtInit = 0b111,
}
// TODO: should the reserved field be preserved?
pub const fn message_address(destination_id: u8, redirect_hint: bool, dest_mode_logical: bool) -> u64 {
0x0000_0000_FEE0_0000u64
| ((destination_id as u64) << 12)
| ((redirect_hint as u64) << 3)
| ((dest_mode_logical as u64) << 2)
}
pub const fn message_data(trigger_mode: TriggerMode, level_trigger_mode: LevelTriggerMode, delivery_mode: DeliveryMode, vector: u8) -> u32 {
((trigger_mode as u32) << 15)
| ((level_trigger_mode as u32) << 14)
| ((delivery_mode as u32) << 8)
| vector as u32
}
pub const fn message_data_level_triggered(level_trigger_mode: LevelTriggerMode, delivery_mode: DeliveryMode, vector: u8) -> u32 {
message_data(TriggerMode::Level, level_trigger_mode, delivery_mode, vector)
}
pub const fn message_data_edge_triggered(delivery_mode: DeliveryMode, vector: u8) -> u32 {
message_data(TriggerMode::Edge, LevelTriggerMode::Deassert, delivery_mode, vector)
}
}
impl MsixTableEntry {
pub fn addr_lo(&self) -> u32 {
self.addr_lo.read()
}
pub fn addr_hi(&self) -> u32 {
self.addr_hi.read()
}
pub fn set_addr_lo(&mut self, value: u32) {
self.addr_lo.write(value);
}
pub fn set_addr_hi(&mut self, value: u32) {
self.addr_hi.write(value);
}
pub fn msg_data(&self) -> u32 {
self.msg_data.read()
}
pub fn vec_ctl(&self) -> u32 {
self.vec_ctl.read()
}
pub fn set_msg_data(&mut self, value: u32) {
self.msg_data.write(value);
}
pub fn addr(&self) -> u64 {
u64::from(self.addr_lo()) | (u64::from(self.addr_hi()) << 32)
}
pub const VEC_CTL_MASK_BIT: u32 = 1;
pub fn set_masked(&mut self, masked: bool) {
self.vec_ctl.writef(Self::VEC_CTL_MASK_BIT, masked)
}
pub fn mask(&mut self) {
self.set_masked(true);
}
pub fn unmask(&mut self) {
self.set_masked(false);
}
pub fn write_addr_and_data(&mut self, entry: MsiAddrAndData) {
self.set_addr_lo(entry.addr as u32);
self.set_addr_hi((entry.addr >> 32) as u32);
self.set_msg_data(entry.data);
}
}
impl fmt::Debug for MsixTableEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("MsixTableEntry")
.field("addr", &self.addr())
.field("msg_data", &self.msg_data())
.field("vec_ctl", &self.vec_ctl())
.finish()
}
}