diff --git a/pci/src/msix.rs b/pci/src/msix.rs new file mode 100644 index 000000000..80a3b9593 --- /dev/null +++ b/pci/src/msix.rs @@ -0,0 +1,212 @@ +// Copyright © 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// + +extern crate byteorder; +extern crate vm_memory; + +use crate::{PciCapability, PciCapabilityID}; +use byteorder::{ByteOrder, LittleEndian}; +use vm_memory::ByteValued; + +const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048; +const MSIX_TABLE_ENTRIES_MODULO: u64 = 16; +const MSIX_PBA_ENTRIES_MODULO: u64 = 8; +const BITS_PER_PBA_ENTRY: usize = 64; + +#[derive(Debug, Clone)] +pub struct MsixTableEntry { + pub msg_addr_lo: u32, + pub msg_addr_hi: u32, + pub msg_data: u32, + pub vector_ctl: u32, +} + +impl Default for MsixTableEntry { + fn default() -> Self { + MsixTableEntry { + msg_addr_lo: 0, + msg_addr_hi: 0, + msg_data: 0, + vector_ctl: 0, + } + } +} + +pub struct MsixConfig { + pub table_entries: Vec, + pub pba_entries: Vec, +} + +impl MsixConfig { + pub fn new(msix_vectors: u16) -> Self { + assert!(msix_vectors < MAX_MSIX_VECTORS_PER_DEVICE); + + let mut table_entries: Vec = Vec::new(); + table_entries.resize_with(msix_vectors as usize, Default::default); + let mut pba_entries: Vec = Vec::new(); + let num_pba_entries: usize = ((msix_vectors as usize) / BITS_PER_PBA_ENTRY) + 1; + pba_entries.resize_with(num_pba_entries, Default::default); + + MsixConfig { + table_entries, + pba_entries, + } + } + + pub fn read_table(&mut self, offset: u64, data: &mut [u8]) { + assert!(data.len() == 4); + + let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize; + let value = match offset % MSIX_TABLE_ENTRIES_MODULO { + 0x0 => self.table_entries[index].msg_addr_lo, + 0x4 => self.table_entries[index].msg_addr_hi, + 0x8 => self.table_entries[index].msg_data, + 0x10 => self.table_entries[index].vector_ctl, + _ => { + error!("invalid offset"); + 0 + } + }; + + debug!("MSI_R TABLE offset 0x{:x} data 0x{:x}", offset, value); + + LittleEndian::write_u32(data, value); + } + + pub fn write_table(&mut self, offset: u64, data: &[u8]) { + assert!(data.len() == 4); + + let value = LittleEndian::read_u32(data); + + let index: usize = (offset / MSIX_TABLE_ENTRIES_MODULO) as usize; + match offset % MSIX_TABLE_ENTRIES_MODULO { + 0x0 => self.table_entries[index].msg_addr_lo = value, + 0x4 => self.table_entries[index].msg_addr_hi = value, + 0x8 => self.table_entries[index].msg_data = value, + 0x10 => self.table_entries[index].vector_ctl = value, + _ => error!("invalid offset"), + }; + + debug!("MSI_W TABLE offset 0x{:x} data 0x{:x}", offset, value); + } + + pub fn read_pba(&mut self, offset: u64, data: &mut [u8]) { + assert!((data.len() == 4 || data.len() == 8)); + + let index: usize = (offset / MSIX_PBA_ENTRIES_MODULO) as usize; + let modulo_offset = offset % MSIX_PBA_ENTRIES_MODULO; + + match data.len() { + 4 => { + let value: u32 = match modulo_offset { + 0x0 => (self.pba_entries[index] & 0xffff_ffffu64) as u32, + 0x4 => (self.pba_entries[index] >> 32) as u32, + _ => { + error!("invalid offset"); + 0 + } + }; + + debug!("MSI_R PBA offset 0x{:x} data 0x{:x}", offset, value); + LittleEndian::write_u32(data, value); + } + 8 => { + let value: u64 = match modulo_offset { + 0x0 => self.pba_entries[index], + _ => { + error!("invalid offset"); + 0 + } + }; + + debug!("MSI_R PBA offset 0x{:x} data 0x{:x}", offset, value); + LittleEndian::write_u64(data, value); + } + _ => { + error!("invalid data length"); + } + } + } + + pub fn write_pba(&mut self, offset: u64, data: &[u8]) { + assert!((data.len() == 4 || data.len() == 8)); + + let index: usize = (offset / MSIX_PBA_ENTRIES_MODULO) as usize; + let modulo_offset = offset % MSIX_PBA_ENTRIES_MODULO; + + match data.len() { + 4 => { + let value = LittleEndian::read_u32(data); + match modulo_offset { + 0x0 => self.pba_entries[index] = u64::from(value), + 0x4 => self.pba_entries[index] = u64::from(value) << 32, + _ => error!("invalid offset"), + } + + debug!("MSI_W PBA offset 0x{:x} data 0x{:x}", offset, value); + } + 8 => { + let value = LittleEndian::read_u64(data); + match modulo_offset { + 0x0 => self.pba_entries[index] = value, + _ => error!("invalid offset"), + } + + debug!("MSI_W PBA offset 0x{:x} data 0x{:x}", offset, value); + } + _ => { + error!("invalid data length"); + } + } + } +} + +#[allow(dead_code)] +#[repr(packed)] +#[derive(Clone, Copy, Default)] +pub struct MsixCap { + // Message Control Register + // 10-0: MSI-X Table size + // 13-11: Reserved + // 14: Mask. Mask all MSI-X when set. + // 15: Enable. Enable all MSI-X when set. + msg_ctl: u16, + // Table. Contains the offset and the BAR indicator (BIR) + // 2-0: Table BAR indicator (BIR). Can be 0 to 5. + // 31-3: Table offset in the BAR pointed by the BIR. + table: u32, + // Pending Bit Array. Contains the offset and the BAR indicator (BIR) + // 2-0: PBA BAR indicator (BIR). Can be 0 to 5. + // 31-3: PBA offset in the BAR pointed by the BIR. + pba: u32, +} + +// It is safe to implement ByteValued. All members are simple numbers and any value is valid. +unsafe impl ByteValued for MsixCap {} + +impl PciCapability for MsixCap { + fn bytes(&self) -> &[u8] { + self.as_slice() + } + + fn id(&self) -> PciCapabilityID { + PciCapabilityID::MSIX + } +} + +impl MsixCap { + pub fn new(pci_bar: u8, table_size: u16, table_off: u32, pba_off: u32) -> Self { + assert!(table_size < MAX_MSIX_VECTORS_PER_DEVICE); + + // Set the table size and enable MSI-X. + let msg_ctl: u16 = 0x8000u16 + table_size - 1; + + MsixCap { + msg_ctl, + table: (table_off & 0xffff_fff8u32) | u32::from(pci_bar & 0x7u8), + pba: (pba_off & 0xffff_fff8u32) | u32::from(pci_bar & 0x7u8), + } + } +}