3fe7b12434
- try_mem now returns Result (matching caller expectations) - try_map_bar: non-panicking BAR mapping on PciFunctionHandle - try_pci_allocate_interrupt_vector: non-panicking IRQ allocation - virtio-core: reverted .ok_or() back to .map_err() for Result type
167 lines
5.4 KiB
Rust
167 lines
5.4 KiB
Rust
use std::fs::File;
|
|
use std::sync::Arc;
|
|
|
|
use pcid_interface::*;
|
|
|
|
use crate::spec::*;
|
|
use crate::transport::{Error, StandardTransport, Transport};
|
|
use crate::utils::align_down;
|
|
|
|
pub struct Device {
|
|
pub transport: Arc<dyn Transport>,
|
|
pub device_space: *const u8,
|
|
pub irq_handle: File,
|
|
}
|
|
|
|
// FIXME(andypython): `device_space` should not be `Send` nor `Sync`. Take
|
|
// it out of `Device`.
|
|
unsafe impl Send for Device {}
|
|
unsafe impl Sync for Device {}
|
|
|
|
pub const MSIX_PRIMARY_VECTOR: u16 = 0;
|
|
|
|
/// VirtIO Device Probe
|
|
///
|
|
/// ## Device State
|
|
/// After this function, the device will have been successfully reseted and is ready for use.
|
|
///
|
|
/// The caller is required to do the following:
|
|
/// * Negotiate the device and driver supported features (finialize via [`StandardTransport::finalize_features`])
|
|
/// * Create the device specific virtio queues (via [`StandardTransport::setup_queue`]). This is *required* to be done
|
|
/// before starting the device.
|
|
/// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device
|
|
/// is alive.
|
|
pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error> {
|
|
let pci_config = pcid_handle.config();
|
|
|
|
if pci_config.func.full_device_id.vendor_id != 6900 {
|
|
log::warn!(
|
|
"virtio_core::probe_device: skipping non-virtio device (vendor ID {:#06x})",
|
|
pci_config.func.full_device_id.vendor_id
|
|
);
|
|
return Err(Error::Probe("not a virtio device"));
|
|
}
|
|
|
|
let mut common_addr = None;
|
|
let mut notify_addr = None;
|
|
let mut device_addr = None;
|
|
|
|
for raw_capability in pcid_handle.get_vendor_capabilities() {
|
|
// SAFETY: We have verified that the length of the data is correct.
|
|
let capability = unsafe { &*(raw_capability.data.as_ptr() as *const PciCapability) };
|
|
|
|
match capability.cfg_type {
|
|
CfgType::Common | CfgType::Notify | CfgType::Device => {}
|
|
_ => continue,
|
|
}
|
|
|
|
let (addr, _) = pci_config.func.bars[capability.bar as usize]
|
|
.try_mem()
|
|
.map_err(|_| Error::Probe("BAR is not memory-mapped"))?;
|
|
|
|
let address = unsafe {
|
|
let addr = addr + capability.offset as usize;
|
|
|
|
// XXX: physmap() requires the address to be page aligned.
|
|
let aligned_addr = align_down(addr);
|
|
let offset = addr - aligned_addr;
|
|
|
|
let size = offset + capability.length as usize;
|
|
|
|
let addr = common::physmap(
|
|
aligned_addr,
|
|
size,
|
|
common::Prot::RW,
|
|
common::MemoryType::Uncacheable,
|
|
)? as usize;
|
|
|
|
addr + offset
|
|
};
|
|
|
|
match capability.cfg_type {
|
|
CfgType::Common => {
|
|
debug_assert!(common_addr.is_none());
|
|
common_addr = Some(address);
|
|
}
|
|
|
|
CfgType::Notify => {
|
|
debug_assert!(notify_addr.is_none());
|
|
|
|
// SAFETY: The capability type is `Notify`, so its safe to access
|
|
// the `notify_multiplier` field.
|
|
let multiplier = unsafe {
|
|
(&*(raw_capability.data.as_ptr() as *const PciCapability
|
|
as *const PciCapabilityNotify))
|
|
.notify_off_multiplier()
|
|
};
|
|
notify_addr = Some((address, multiplier));
|
|
}
|
|
|
|
CfgType::Device => {
|
|
debug_assert!(device_addr.is_none());
|
|
device_addr = Some(address);
|
|
}
|
|
|
|
_ => continue,
|
|
}
|
|
}
|
|
|
|
let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?;
|
|
let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?;
|
|
let (notify_addr, notify_multiplier) =
|
|
notify_addr.ok_or(Error::InCapable(CfgType::Notify))?;
|
|
|
|
// The virtio specification explicitly allows a zero notify_off_multiplier,
|
|
// meaning all queues share the same notification address. Handle gracefully.
|
|
if notify_multiplier == 0 {
|
|
log::warn!(
|
|
"virtio_core::probe_device: device uses the same Queue Notify address for all queues"
|
|
);
|
|
return Err(Error::Probe("zero notify_off_multiplier"));
|
|
}
|
|
|
|
let common = unsafe { &mut *(common_addr as *mut CommonCfg) };
|
|
let device_space = unsafe { &mut *(device_addr as *mut u8) };
|
|
|
|
let transport = StandardTransport::new(
|
|
common,
|
|
notify_addr as *const u8,
|
|
notify_multiplier,
|
|
device_space,
|
|
);
|
|
|
|
// Setup interrupts.
|
|
let all_pci_features = pcid_handle.fetch_all_features();
|
|
let has_msix = all_pci_features.iter().any(|feature| feature.is_msix());
|
|
|
|
if !has_msix {
|
|
log::warn!("virtio_core::probe_device: device does not support MSI-X");
|
|
return Err(Error::Probe("device does not support MSI-X"));
|
|
}
|
|
let irq_handle = crate::arch::enable_msix(pcid_handle)?;
|
|
|
|
log::debug!("virtio: using standard PCI transport");
|
|
|
|
let device = Device {
|
|
transport,
|
|
device_space,
|
|
irq_handle,
|
|
};
|
|
|
|
device.transport.reset();
|
|
reinit(&device)?;
|
|
|
|
Ok(device)
|
|
}
|
|
|
|
pub fn reinit(device: &Device) -> Result<(), Error> {
|
|
// XXX: According to the virtio specification v1.2, setting the ACKNOWLEDGE and DRIVER bits
|
|
// in `device_status` is required to be done in two steps.
|
|
device
|
|
.transport
|
|
.insert_status(DeviceStatusFlags::ACKNOWLEDGE);
|
|
|
|
device.transport.insert_status(DeviceStatusFlags::DRIVER);
|
|
Ok(())
|
|
}
|