use crate::device::{
bus::{Request, RequestSize, SingleThreadedBusDevice},
msi_message::MsiMessage,
pci::constants::config_space,
register_set::{RegisterSet, RegisterSetBuilder},
};
pub const MSIX_ENTRY_SIZE: usize = 16;
pub mod offset {
pub const MESSAGE_ADDRESS: usize = 0;
pub const MESSAGE_DATA: usize = 2 * 4;
pub const CONTROL: usize = 3 * 4;
}
pub const CONTROL_MASKED: u32 = 1 << 0;
#[derive(Debug)]
pub struct MsixTable<const SIZE_BYTES: usize> {
registers: RegisterSet<{ SIZE_BYTES }>,
}
impl<const SIZE_BYTES: usize> MsixTable<SIZE_BYTES> {
#[must_use]
pub fn new() -> Self {
assert_eq!(
SIZE_BYTES % MSIX_ENTRY_SIZE,
0,
"The MSI-X table size must be an integer multiple of MSIX_ENTRY_SIZE"
);
assert!(SIZE_BYTES > 0);
assert!(SIZE_BYTES <= usize::from(config_space::msix::MAX_VECTORS) * MSIX_ENTRY_SIZE);
let mut builder = RegisterSetBuilder::<{ SIZE_BYTES }>::new();
(0..usize::from(Self::vector_count()))
.map(|v| v * MSIX_ENTRY_SIZE)
.for_each(|offset| {
builder
.u64_le_rw_at(offset + offset::MESSAGE_ADDRESS, 0)
.u32_le_rw_at(offset + offset::MESSAGE_DATA, 0)
.u32_le_rw_at(offset + offset::CONTROL, CONTROL_MASKED);
});
Self {
registers: builder.into(),
}
}
#[must_use]
pub const fn vector_count() -> u16 {
(SIZE_BYTES / MSIX_ENTRY_SIZE) as u16
}
#[must_use]
#[allow(unused)]
pub fn vector(&self, vector: u16) -> Option<MsiMessage> {
assert!(vector < Self::vector_count());
let entry_offset = u64::from(vector) * u64::try_from(MSIX_ENTRY_SIZE).unwrap();
let field_read = |foffset: usize, size: RequestSize| {
self.registers.read(Request::new(
entry_offset + u64::try_from(foffset).unwrap(),
size,
))
};
let control = field_read(offset::CONTROL, RequestSize::Size1);
(control & u64::from(CONTROL_MASKED) == 0).then(|| {
MsiMessage::new(
field_read(offset::MESSAGE_ADDRESS, RequestSize::Size8),
field_read(offset::MESSAGE_DATA, RequestSize::Size2)
.try_into()
.unwrap(),
)
})
}
}
impl<const VECTORS: usize> Default for MsixTable<VECTORS> {
fn default() -> Self {
Self::new()
}
}
impl<const VECTORS: usize> SingleThreadedBusDevice for MsixTable<VECTORS> {
fn size(&self) -> u64 {
self.registers.size()
}
fn read(&mut self, req: Request) -> u64 {
self.registers.read(req)
}
fn write(&mut self, req: Request, value: u64) {
self.registers.write(req, value);
}
}
#[cfg(test)]
mod tests {
use super::*;
type ExampleTable = MsixTable<{ 16 * MSIX_ENTRY_SIZE }>;
#[test]
fn vector_count_is_correctly_computed() {
assert_eq!(ExampleTable::vector_count(), 16);
}
#[test]
fn all_vectors_are_masked_by_default() {
let mut table = ExampleTable::new();
assert!((0..ExampleTable::vector_count())
.map(|v| u64::from(v) * (MSIX_ENTRY_SIZE as u64) + (offset::CONTROL as u64))
.map(|offset| table.read(Request::new(offset, RequestSize::Size4)))
.all(|control| control & u64::from(CONTROL_MASKED) != 0));
assert_eq!(
(0..ExampleTable::vector_count())
.filter_map(|v| table.vector(v))
.count(),
0
);
}
#[test]
fn configured_vectors_are_visible() {
let example_address = 0xcafe_d00d_feed_face;
let example_data: u16 = 0xbeef;
let mut table = ExampleTable::new();
let entry_1_offset: usize = MSIX_ENTRY_SIZE;
table.write(
Request::new(
(entry_1_offset + offset::MESSAGE_ADDRESS) as u64,
RequestSize::Size8,
),
example_address,
);
table.write(
Request::new(
(entry_1_offset + offset::MESSAGE_DATA) as u64,
RequestSize::Size4,
),
example_data.into(),
);
assert_eq!(table.vector(1), None);
table.write(
Request::new(
(entry_1_offset + offset::CONTROL) as u64,
RequestSize::Size4,
),
0,
);
assert_eq!(
table.vector(1),
Some(MsiMessage {
address: example_address,
data: example_data,
})
);
}
}