use crate::device::NotPciDevice;
use crate::device::VpciChannel;
use crate::device::VpciConfigSpace;
use crate::device::VpciConfigSpaceOffset;
use crate::protocol;
use crate::protocol::SlotNumber;
use chipset_device::ChipsetDevice;
use chipset_device::io::IoError;
use chipset_device::io::IoResult;
use chipset_device::mmio::MmioIntercept;
use chipset_device::mmio::RegisterMmioIntercept;
use closeable_mutex::CloseableMutex;
use device_emulators::ReadWriteRequestType;
use device_emulators::read_as_u32_chunks;
use device_emulators::write_as_u32_chunks;
use guid::Guid;
use hvdef::HV_PAGE_SIZE;
use inspect::InspectMut;
use std::sync::Arc;
use thiserror::Error;
use vmbus_channel::simple::SimpleDeviceHandle;
use vmbus_channel::simple::offer_simple_device;
use vmcore::device_state::ChangeDeviceState;
use vmcore::save_restore::NoSavedState;
use vmcore::save_restore::RestoreError;
use vmcore::save_restore::SaveError;
use vmcore::save_restore::SaveRestore;
use vmcore::vm_task::VmTaskDriverSource;
use vmcore::vpci_msi::VpciInterruptMapper;
#[derive(InspectMut)]
pub struct VpciBus {
#[inspect(skip)]
device: Arc<CloseableMutex<dyn ChipsetDevice>>,
#[inspect(flatten)]
channel: SimpleDeviceHandle<VpciChannel>,
config_space_offset: VpciConfigSpaceOffset,
#[inspect(with = "|&x| u32::from(x)")]
current_slot: SlotNumber,
}
#[derive(Debug, Error)]
pub enum CreateBusError {
#[error(transparent)]
NotPci(NotPciDevice),
#[error("failed to offer vpci vmbus channel")]
Offer(#[source] anyhow::Error),
}
impl VpciBus {
pub async fn new(
driver_source: &VmTaskDriverSource,
instance_id: Guid,
device: Arc<CloseableMutex<dyn ChipsetDevice>>,
register_mmio: &mut dyn RegisterMmioIntercept,
vmbus: &dyn vmbus_channel::bus::ParentBus,
msi_controller: Arc<dyn VpciInterruptMapper>,
) -> Result<Self, CreateBusError> {
let config_space = VpciConfigSpace::new(
register_mmio.new_io_region(&format!("vpci-{instance_id}-config"), 2 * HV_PAGE_SIZE),
);
let config_space_offset = config_space.offset().clone();
let channel = VpciChannel::new(&device, instance_id, config_space, msi_controller)
.map_err(CreateBusError::NotPci)?;
let channel = offer_simple_device(driver_source, vmbus, channel)
.await
.map_err(CreateBusError::Offer)?;
Ok(Self {
device,
channel,
config_space_offset,
current_slot: SlotNumber::from(0),
})
}
}
impl ChangeDeviceState for VpciBus {
fn start(&mut self) {
self.channel.start();
}
async fn stop(&mut self) {
self.channel.stop().await;
}
async fn reset(&mut self) {
self.channel.reset().await;
}
}
impl SaveRestore for VpciBus {
type SavedState = NoSavedState;
fn save(&mut self) -> Result<Self::SavedState, SaveError> {
Ok(NoSavedState)
}
fn restore(&mut self, NoSavedState: Self::SavedState) -> Result<(), RestoreError> {
Ok(())
}
}
impl ChipsetDevice for VpciBus {
fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
Some(self)
}
}
impl MmioIntercept for VpciBus {
fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
let reg = match self.register(addr, data.len()) {
Ok(reg) => reg,
Err(err) => return IoResult::Err(err),
};
match reg {
Register::SlotNumber => return IoResult::Err(IoError::InvalidRegister),
Register::ConfigSpace(offset) => {
if u32::from(self.current_slot) == 0 {
let mut device = self.device.lock();
let pci = device.supports_pci().unwrap();
let mut buf = 0;
read_as_u32_chunks(offset, data, |addr| {
pci.pci_cfg_read(addr, &mut buf)
.now_or_never()
.map(|_| buf)
.unwrap_or(0)
});
} else {
tracelimit::warn_ratelimited!(slot = ?self.current_slot, offset, "no device at slot for config space read");
data.fill(!0);
}
}
}
IoResult::Ok
}
fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
let reg = match self.register(addr, data.len()) {
Ok(reg) => reg,
Err(err) => return IoResult::Err(err),
};
match reg {
Register::SlotNumber => {
let Ok(data) = data.try_into().map(u32::from_ne_bytes) else {
return IoResult::Err(IoError::InvalidAccessSize);
};
self.current_slot = SlotNumber::from(data);
}
Register::ConfigSpace(offset) => {
if u32::from(self.current_slot) == 0 {
let mut device = self.device.lock();
let pci = device.supports_pci().unwrap();
let mut buf = 0;
write_as_u32_chunks(offset, data, |address, request_type| match request_type {
ReadWriteRequestType::Write(value) => {
pci.pci_cfg_write(address, value).unwrap();
None
}
ReadWriteRequestType::Read => Some(
pci.pci_cfg_read(address, &mut buf)
.now_or_never()
.map(|_| buf)
.unwrap_or(0),
),
});
} else {
tracelimit::warn_ratelimited!(slot = ?self.current_slot, offset, "no device at slot for config space write");
}
}
}
IoResult::Ok
}
}
enum Register {
SlotNumber,
ConfigSpace(u16),
}
impl VpciBus {
fn register(&self, addr: u64, len: usize) -> Result<Register, IoError> {
let config_base = self
.config_space_offset
.get()
.ok_or(IoError::InvalidRegister)?;
let offset = addr.wrapping_sub(config_base);
let page = offset & protocol::MMIO_PAGE_MASK;
let offset_in_page = (offset & !protocol::MMIO_PAGE_MASK) as u16;
if (offset_in_page as u64 + len as u64) & protocol::MMIO_PAGE_MASK != 0 {
return Err(IoError::InvalidAccessSize);
}
let reg = match page {
protocol::MMIO_PAGE_SLOT_NUMBER => {
if offset_in_page != 0 {
return Err(IoError::InvalidRegister);
}
if len != 4 {
return Err(IoError::InvalidAccessSize);
}
Register::SlotNumber
}
protocol::MMIO_PAGE_CONFIG_SPACE => Register::ConfigSpace(offset_in_page),
_ => return Err(IoError::InvalidRegister),
};
Ok(reg)
}
}