Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions axdriver_pci/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,72 @@ pub use virtio_drivers::transport::pci::bus::{
MemoryBarType, PciError, PciRoot, Status,
};

/// Provides read/write access to PCI configuration space registers.
///
/// The `virtio-drivers` crate exposes `PciRoot` but keeps its internal
/// config-space access helpers private. This type re-implements the same MMIO
/// access so callers can read arbitrary PCI config registers such as
/// `Interrupt Line`.
pub struct PciConfigAccess {
mmio_base: *mut u32,
cam: Cam,
}

// SAFETY: PciConfigAccess is used like PciRoot: the raw pointer points to a
// static MMIO mapping that is valid for the lifetime of the program.
unsafe impl Send for PciConfigAccess {}
unsafe impl Sync for PciConfigAccess {}

impl PciConfigAccess {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not reuse Transport::read_config_space from the virtio-drivers crate?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change needs the PCI config-space Interrupt Line register (0x3C), not the VirtIO device-specific config space exposed by the transport. Transport::config_space is for VirtIO device config, while PciConfigAccess is for PCI header/config-space access during PCI probe.

/// Creates a new PCI config-space accessor from the ECAM/MMIO base address.
///
/// # Safety
///
/// `mmio_base` must point to a valid mapped PCI configuration window.
pub unsafe fn new(mmio_base: *mut u8, cam: Cam) -> Self {
assert!(mmio_base as usize & 0x3 == 0);
Self {
mmio_base: mmio_base as *mut u32,
cam,
}
}

fn cam_offset(&self, device_function: DeviceFunction, register_offset: u8) -> u32 {
assert!(device_function.valid());

let bdf = (device_function.bus as u32) << 8
| (device_function.device as u32) << 3
| device_function.function as u32;
let address =
bdf << match self.cam {
Cam::MmioCam => 8,
Cam::Ecam => 12,
} | (register_offset as u32 & !0x3);
assert!(address < self.cam.size());
assert!(address & 0x3 == 0);
address
}

/// Reads a 32-bit word from PCI configuration space.
pub fn read_word(&self, device_function: DeviceFunction, register_offset: u8) -> u32 {
let address = self.cam_offset(device_function, register_offset);
// SAFETY: The pointer arithmetic stays within the MMIO window because
// cam_offset() produces offsets bounded by Cam::size().
unsafe { self.mmio_base.add((address >> 2) as usize).read_volatile() }
}

/// Writes a 32-bit word to PCI configuration space.
pub fn write_word(&mut self, device_function: DeviceFunction, register_offset: u8, data: u32) {
let address = self.cam_offset(device_function, register_offset);
// SAFETY: Same as read_word.
unsafe {
self.mmio_base
.add((address >> 2) as usize)
.write_volatile(data)
}
}
}

/// Used to allocate MMIO regions for PCI BARs.
pub struct PciRangeAllocator {
_start: u64,
Expand Down
1 change: 1 addition & 0 deletions axdriver_virtio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ axdriver_base = { workspace = true }
axdriver_block = { workspace = true, optional = true }
axdriver_display = { workspace = true, optional = true }
axdriver_net = { workspace = true, optional = true }
axdriver_pci = { workspace = true }
log = { workspace = true }
virtio-drivers = { version = "0.7.5", default-features = false }
30 changes: 28 additions & 2 deletions axdriver_virtio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod gpu;
mod net;

use axdriver_base::{DevError, DeviceType};
use axdriver_pci::PciConfigAccess;
use virtio_drivers::transport::DeviceType as VirtIoDevType;
pub use virtio_drivers::{
BufferDirection, Hal as VirtIoHal, PhysAddr,
Expand Down Expand Up @@ -70,12 +71,37 @@ pub fn probe_pci_device<H: VirtIoHal>(
root: &mut PciRoot,
bdf: DeviceFunction,
dev_info: &DeviceFunctionInfo,
) -> Option<(DeviceType, PciTransport)> {
config: &PciConfigAccess,
) -> Option<(DeviceType, PciTransport, usize)> {
use virtio_drivers::transport::pci::virtio_device_type;

let dev_type = virtio_device_type(dev_info).and_then(as_dev_type)?;
let transport = PciTransport::new::<H>(root, bdf).ok()?;
Some((dev_type, transport))
#[cfg(target_arch = "x86_64")]
let irq = legacy_irq_for_bdf(config, bdf);
#[cfg(target_arch = "riscv64")]
let irq = 0x20 + (bdf.device & 3) as usize;
#[cfg(target_arch = "loongarch64")]
let irq = 0x10 + (bdf.device & 3) as usize;
#[cfg(target_arch = "aarch64")]
let irq = 0x23 + (bdf.device & 3) as usize;

#[cfg(target_arch = "x86_64")]
if irq == 0 || irq == 0xff {
log::warn!(
"PCI device {:?}: Interrupt Line not assigned ({:#x})",
bdf,
irq
);
return None;
}

Some((dev_type, transport, irq))
}

#[cfg(target_arch = "x86_64")]
fn legacy_irq_for_bdf(config: &PciConfigAccess, bdf: DeviceFunction) -> usize {
(config.read_word(bdf, 0x3c) & 0xff) as usize
}

const fn as_dev_type(t: VirtIoDevType) -> Option<DeviceType> {
Expand Down