mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-28 20:03:22 +00:00
Refactor project structure
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
bd878dd1c9
commit
e3c227ae06
293
kernel/comps/virtio/src/transport/mmio/device.rs
Normal file
293
kernel/comps/virtio/src/transport/mmio/device.rs
Normal file
@ -0,0 +1,293 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use core::mem::size_of;
|
||||
|
||||
use aster_frame::{
|
||||
bus::mmio::{
|
||||
bus::MmioDevice,
|
||||
device::{MmioCommonDevice, VirtioMmioVersion},
|
||||
},
|
||||
config::PAGE_SIZE,
|
||||
io_mem::IoMem,
|
||||
offset_of,
|
||||
sync::RwLock,
|
||||
trap::IrqCallbackFunction,
|
||||
vm::DmaCoherent,
|
||||
};
|
||||
use aster_rights::{ReadOp, WriteOp};
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use log::warn;
|
||||
|
||||
use super::{layout::VirtioMmioLayout, multiplex::MultiplexIrq};
|
||||
use crate::{
|
||||
queue::{AvailRing, Descriptor, UsedRing},
|
||||
transport::{DeviceStatus, VirtioTransport, VirtioTransportError},
|
||||
VirtioDeviceType,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtioMmioDevice {
|
||||
device_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtioMmioTransport {
|
||||
layout: SafePtr<VirtioMmioLayout, IoMem>,
|
||||
device: Arc<VirtioMmioDevice>,
|
||||
common_device: aster_frame::bus::mmio::device::MmioCommonDevice,
|
||||
multiplex: Arc<RwLock<MultiplexIrq>>,
|
||||
}
|
||||
|
||||
impl MmioDevice for VirtioMmioDevice {
|
||||
fn device_id(&self) -> u32 {
|
||||
self.device_id
|
||||
}
|
||||
}
|
||||
|
||||
impl MmioDevice for VirtioMmioTransport {
|
||||
fn device_id(&self) -> u32 {
|
||||
self.common_device.device_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioMmioTransport {
|
||||
pub(super) fn mmio_device(&self) -> &Arc<VirtioMmioDevice> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
pub(super) fn new(device: MmioCommonDevice) -> Self {
|
||||
let irq = device.irq().clone();
|
||||
let layout = SafePtr::new(device.io_mem().clone(), 0);
|
||||
let device_id = device.device_id();
|
||||
let (interrupt_ack, interrupt_status) = {
|
||||
let interrupt_ack_offset = offset_of!(VirtioMmioLayout, interrupt_ack);
|
||||
let interrupt_status_offset = offset_of!(VirtioMmioLayout, interrupt_status);
|
||||
let mut interrupt_ack = layout.clone();
|
||||
interrupt_ack.byte_add(interrupt_ack_offset as usize);
|
||||
let mut interrupt_status = layout.clone();
|
||||
interrupt_status.byte_add(interrupt_status_offset as usize);
|
||||
(
|
||||
interrupt_ack.cast::<u32>().restrict::<WriteOp>(),
|
||||
interrupt_status.cast::<u32>().restrict::<ReadOp>(),
|
||||
)
|
||||
};
|
||||
let device = Self {
|
||||
layout,
|
||||
common_device: device,
|
||||
multiplex: MultiplexIrq::new(irq, interrupt_ack, interrupt_status),
|
||||
device: Arc::new(VirtioMmioDevice { device_id }),
|
||||
};
|
||||
if device.common_device.version() == VirtioMmioVersion::Legacy {
|
||||
field_ptr!(&device.layout, VirtioMmioLayout, legacy_guest_page_size)
|
||||
.write(&(PAGE_SIZE as u32))
|
||||
.unwrap();
|
||||
}
|
||||
device
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioTransport for VirtioMmioTransport {
|
||||
fn device_type(&self) -> VirtioDeviceType {
|
||||
VirtioDeviceType::try_from(self.common_device.device_id() as u8).unwrap()
|
||||
}
|
||||
|
||||
fn set_queue(
|
||||
&mut self,
|
||||
idx: u16,
|
||||
queue_size: u16,
|
||||
descriptor_ptr: &SafePtr<Descriptor, DmaCoherent>,
|
||||
driver_ptr: &SafePtr<AvailRing, DmaCoherent>,
|
||||
device_ptr: &SafePtr<UsedRing, DmaCoherent>,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_sel)
|
||||
.write(&(idx as u32))
|
||||
.unwrap();
|
||||
|
||||
let queue_num_max: u32 = field_ptr!(&self.layout, VirtioMmioLayout, queue_num_max)
|
||||
.read()
|
||||
.unwrap();
|
||||
|
||||
if queue_size as u32 > queue_num_max {
|
||||
warn!("Set queue failed, queue size is bigger than maximum virtual queue size.");
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
}
|
||||
|
||||
let descriptor_paddr = descriptor_ptr.paddr();
|
||||
let driver_paddr = driver_ptr.paddr();
|
||||
let device_paddr = device_ptr.paddr();
|
||||
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_num)
|
||||
.write(&(queue_size as u32))
|
||||
.unwrap();
|
||||
|
||||
match self.common_device.version() {
|
||||
VirtioMmioVersion::Legacy => {
|
||||
// The area should be continuous
|
||||
assert_eq!(
|
||||
driver_paddr - descriptor_paddr,
|
||||
size_of::<Descriptor>() * queue_size as usize
|
||||
);
|
||||
// Descriptor paddr should align
|
||||
assert_eq!(descriptor_paddr % PAGE_SIZE, 0);
|
||||
let pfn = (descriptor_paddr / PAGE_SIZE) as u32;
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, legacy_queue_align)
|
||||
.write(&(PAGE_SIZE as u32))
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, legacy_queue_pfn)
|
||||
.write(&pfn)
|
||||
.unwrap();
|
||||
}
|
||||
VirtioMmioVersion::Modern => {
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_desc_low)
|
||||
.write(&(descriptor_paddr as u32))
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_desc_high)
|
||||
.write(&((descriptor_paddr >> 32) as u32))
|
||||
.unwrap();
|
||||
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_driver_low)
|
||||
.write(&(driver_paddr as u32))
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_driver_high)
|
||||
.write(&((driver_paddr >> 32) as u32))
|
||||
.unwrap();
|
||||
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_device_low)
|
||||
.write(&(device_paddr as u32))
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_device_high)
|
||||
.write(&((device_paddr >> 32) as u32))
|
||||
.unwrap();
|
||||
// enable queue
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_sel)
|
||||
.write(&(idx as u32))
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_ready)
|
||||
.write(&1u32)
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_notify_ptr(&self, _idx: u16) -> Result<SafePtr<u32, IoMem>, VirtioTransportError> {
|
||||
let offset = offset_of!(VirtioMmioLayout, queue_notify) as usize;
|
||||
Ok(SafePtr::new(self.common_device.io_mem().clone(), offset))
|
||||
}
|
||||
|
||||
fn num_queues(&self) -> u16 {
|
||||
// We use the field `queue_num_max` to get queue size.
|
||||
// If the queue is not exists, the field should be zero
|
||||
let mut num_queues = 0;
|
||||
const MAX_QUEUES: u32 = 512;
|
||||
while num_queues < MAX_QUEUES {
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_sel)
|
||||
.write(&num_queues)
|
||||
.unwrap();
|
||||
if field_ptr!(&self.layout, VirtioMmioLayout, queue_num_max)
|
||||
.read()
|
||||
.unwrap()
|
||||
== 0u32
|
||||
{
|
||||
return num_queues as u16;
|
||||
}
|
||||
num_queues += 1;
|
||||
}
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn device_config_memory(&self) -> IoMem {
|
||||
// offset: 0x100~0x200
|
||||
let mut io_mem = self.common_device.io_mem().clone();
|
||||
let paddr = io_mem.paddr();
|
||||
io_mem.resize((paddr + 0x100)..(paddr + 0x200)).unwrap();
|
||||
io_mem
|
||||
}
|
||||
|
||||
fn device_features(&self) -> u64 {
|
||||
// select low
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, device_features_select)
|
||||
.write(&0u32)
|
||||
.unwrap();
|
||||
let device_feature_low = field_ptr!(&self.layout, VirtioMmioLayout, device_features)
|
||||
.read()
|
||||
.unwrap();
|
||||
// select high
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, device_features_select)
|
||||
.write(&1u32)
|
||||
.unwrap();
|
||||
let device_feature_high = field_ptr!(&self.layout, VirtioMmioLayout, device_features)
|
||||
.read()
|
||||
.unwrap() as u64;
|
||||
device_feature_high << 32 | device_feature_low as u64
|
||||
}
|
||||
|
||||
fn set_driver_features(&mut self, features: u64) -> Result<(), VirtioTransportError> {
|
||||
let low = features as u32;
|
||||
let high = (features >> 32) as u32;
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, driver_features_select)
|
||||
.write(&0u32)
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, driver_features)
|
||||
.write(&low)
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, driver_features_select)
|
||||
.write(&1u32)
|
||||
.unwrap();
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, driver_features)
|
||||
.write(&high)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn device_status(&self) -> DeviceStatus {
|
||||
DeviceStatus::from_bits(
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, status)
|
||||
.read()
|
||||
.unwrap() as u8,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn set_device_status(&mut self, status: DeviceStatus) -> Result<(), VirtioTransportError> {
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, status)
|
||||
.write(&(status.bits() as u32))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_legacy_version(&self) -> bool {
|
||||
self.common_device.version() == VirtioMmioVersion::Legacy
|
||||
}
|
||||
|
||||
fn max_queue_size(&self, idx: u16) -> Result<u16, VirtioTransportError> {
|
||||
field_ptr!(&self.layout, VirtioMmioLayout, queue_sel)
|
||||
.write(&(idx as u32))
|
||||
.unwrap();
|
||||
Ok(field_ptr!(&self.layout, VirtioMmioLayout, queue_num_max)
|
||||
.read()
|
||||
.unwrap() as u16)
|
||||
}
|
||||
|
||||
fn register_queue_callback(
|
||||
&mut self,
|
||||
_index: u16,
|
||||
func: Box<IrqCallbackFunction>,
|
||||
single_interrupt: bool,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
if single_interrupt {
|
||||
return Err(VirtioTransportError::NotEnoughResources);
|
||||
}
|
||||
self.multiplex.write().register_queue_callback(func);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_cfg_callback(
|
||||
&mut self,
|
||||
func: Box<IrqCallbackFunction>,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
self.multiplex.write().register_cfg_callback(func);
|
||||
Ok(())
|
||||
}
|
||||
}
|
49
kernel/comps/virtio/src/transport/mmio/driver.rs
Normal file
49
kernel/comps/virtio/src/transport/mmio/driver.rs
Normal file
@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
|
||||
use aster_frame::{
|
||||
bus::{
|
||||
mmio::{
|
||||
bus::{MmioDevice, MmioDriver},
|
||||
device::MmioCommonDevice,
|
||||
},
|
||||
BusProbeError,
|
||||
},
|
||||
sync::SpinLock,
|
||||
};
|
||||
|
||||
use super::device::VirtioMmioTransport;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtioMmioDriver {
|
||||
devices: SpinLock<Vec<VirtioMmioTransport>>,
|
||||
}
|
||||
|
||||
impl VirtioMmioDriver {
|
||||
pub fn num_devices(&self) -> usize {
|
||||
self.devices.lock().len()
|
||||
}
|
||||
|
||||
pub fn pop_device_transport(&self) -> Option<VirtioMmioTransport> {
|
||||
self.devices.lock().pop()
|
||||
}
|
||||
|
||||
pub(super) fn new() -> Self {
|
||||
VirtioMmioDriver {
|
||||
devices: SpinLock::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MmioDriver for VirtioMmioDriver {
|
||||
fn probe(
|
||||
&self,
|
||||
device: MmioCommonDevice,
|
||||
) -> Result<Arc<dyn MmioDevice>, (BusProbeError, MmioCommonDevice)> {
|
||||
let device = VirtioMmioTransport::new(device);
|
||||
let mmio_device = device.mmio_device().clone();
|
||||
self.devices.lock().push(device);
|
||||
Ok(mmio_device)
|
||||
}
|
||||
}
|
145
kernel/comps/virtio/src/transport/mmio/layout.rs
Normal file
145
kernel/comps/virtio/src/transport/mmio/layout.rs
Normal file
@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
use pod::Pod;
|
||||
|
||||
#[derive(Clone, Copy, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioMmioLayout {
|
||||
/// Magic value: 0x74726976. **Read-only**
|
||||
pub magic_value: u32,
|
||||
/// Device version. 1 => Legacy, 2 => Normal. **Read-only**
|
||||
pub version: u32,
|
||||
/// Virtio Subsystem Device ID. **Read-only**
|
||||
pub device_id: u32,
|
||||
/// Virtio Subsystem Vendor ID. **Read-only**
|
||||
pub vendor_id: u32,
|
||||
|
||||
/// Flags representing features the device supports.
|
||||
/// Bits 0-31 if `device_features_sel`is 0,
|
||||
/// bits 32-63 if `device_features_sel` is 1.
|
||||
/// **Read-only**
|
||||
pub device_features: u32,
|
||||
/// **Write-only**
|
||||
pub device_features_select: u32,
|
||||
|
||||
__r1: [u8; 8],
|
||||
|
||||
/// Flags representing device features understood and activated by the driver.
|
||||
/// Bits 0-31 if `driver_features_sel`is 0,
|
||||
/// bits 32-63 if `driver_features_sel` is 1.
|
||||
/// **Write-only**
|
||||
pub driver_features: u32,
|
||||
/// **Write-only**
|
||||
pub driver_features_select: u32,
|
||||
|
||||
/// Guest page size.
|
||||
///
|
||||
/// The driver writes the guest page size in bytes
|
||||
/// to the register during initialization, before any queues are used.
|
||||
///
|
||||
/// This value should be a power of 2 and is used by the device to
|
||||
/// calculate the Guest address of the first queue page (see legacy_queue_pfn).
|
||||
/// **Write-only**
|
||||
pub legacy_guest_page_size: u32,
|
||||
|
||||
__r2: [u8; 4],
|
||||
|
||||
/// Selected queue. **Write-only**
|
||||
pub queue_sel: u32,
|
||||
/// Maximum virtual queue size. **Read-only**
|
||||
pub queue_num_max: u32,
|
||||
/// Virtual queue size. **Write-only**
|
||||
pub queue_num: u32,
|
||||
|
||||
pub legacy_queue_align: u32,
|
||||
|
||||
pub legacy_queue_pfn: u32,
|
||||
|
||||
/// Virtual queue ready bit.
|
||||
///
|
||||
/// Write 1 to notifies the device that it can execute requests from this virtual queue.
|
||||
/// **Read-Write**
|
||||
pub queue_ready: u32,
|
||||
|
||||
__r3: [u8; 8],
|
||||
|
||||
/// Queue notifier.
|
||||
///
|
||||
/// Writing a value to this register notifies the device
|
||||
/// that there are new buffers to process in a queue. **Write-only**
|
||||
pub queue_notify: u32,
|
||||
|
||||
__r4: [u8; 12],
|
||||
|
||||
/// Interrupt status.
|
||||
///
|
||||
/// bit0 => Used Buffer Notification;
|
||||
/// bit1 => Configuration Change Notification
|
||||
/// **Read-only**
|
||||
pub interrupt_status: u32,
|
||||
/// Interrupt acknowledge. **Write-only**
|
||||
pub interrupt_ack: u32,
|
||||
|
||||
__r5: [u8; 8],
|
||||
|
||||
/// Device status. **Read-Write**
|
||||
pub status: u32,
|
||||
|
||||
__r6: [u8; 12],
|
||||
|
||||
/// Virtual queue’s Descriptor Area 64 bit long physical address. **Write-only**
|
||||
pub queue_desc_low: u32,
|
||||
pub queue_desc_high: u32,
|
||||
|
||||
__r7: [u8; 8],
|
||||
|
||||
/// Virtual queue’s Driver Area 64 bit long physical address. **Write-only**
|
||||
pub queue_driver_low: u32,
|
||||
pub queue_driver_high: u32,
|
||||
|
||||
__r8: [u8; 8],
|
||||
|
||||
/// Virtual queue’s Device Area 64 bit long physical address. **Write-only**
|
||||
pub queue_device_low: u32,
|
||||
pub queue_device_high: u32,
|
||||
|
||||
__r9: [u8; 84],
|
||||
|
||||
/// Configuration atomicity value. **Read-only**
|
||||
pub config_generation: u32,
|
||||
}
|
||||
|
||||
impl Debug for VirtioMmioLayout {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("VirtioMmioLayout")
|
||||
.field("magic_value", &self.magic_value)
|
||||
.field("version", &self.version)
|
||||
.field("device_id", &self.device_id)
|
||||
.field("vendor_id", &self.vendor_id)
|
||||
.field("device_features", &self.device_features)
|
||||
.field("device_features_sel", &self.device_features_select)
|
||||
.field("driver_features", &self.driver_features)
|
||||
.field("driver_features_sel", &self.driver_features_select)
|
||||
.field("legacy_guest_page_size", &self.legacy_guest_page_size)
|
||||
.field("queue_sel", &self.queue_sel)
|
||||
.field("queue_num_max", &self.queue_num_max)
|
||||
.field("queue_num", &self.queue_num)
|
||||
.field("legacy_queue_align", &self.legacy_queue_align)
|
||||
.field("legacy_queue_pfn", &self.legacy_queue_pfn)
|
||||
.field("queue_ready", &self.queue_ready)
|
||||
.field("queue_notify", &self.queue_notify)
|
||||
.field("interrupt_status", &self.interrupt_status)
|
||||
.field("interrupt_ack", &self.interrupt_ack)
|
||||
.field("status", &self.status)
|
||||
.field("queue_desc_low", &self.queue_desc_low)
|
||||
.field("queue_desc_high", &self.queue_desc_high)
|
||||
.field("queue_driver_low", &self.queue_driver_low)
|
||||
.field("queue_driver_high", &self.queue_driver_high)
|
||||
.field("queue_device_low", &self.queue_device_low)
|
||||
.field("queue_device_high", &self.queue_device_high)
|
||||
.field("config_generation", &self.config_generation)
|
||||
.finish()
|
||||
}
|
||||
}
|
21
kernel/comps/virtio/src/transport/mmio/mod.rs
Normal file
21
kernel/comps/virtio/src/transport/mmio/mod.rs
Normal file
@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use aster_frame::bus::mmio::MMIO_BUS;
|
||||
use spin::Once;
|
||||
|
||||
use self::driver::VirtioMmioDriver;
|
||||
|
||||
pub mod device;
|
||||
pub mod driver;
|
||||
pub mod layout;
|
||||
pub mod multiplex;
|
||||
|
||||
pub static VIRTIO_MMIO_DRIVER: Once<Arc<VirtioMmioDriver>> = Once::new();
|
||||
pub fn virtio_mmio_init() {
|
||||
VIRTIO_MMIO_DRIVER.call_once(|| Arc::new(VirtioMmioDriver::new()));
|
||||
MMIO_BUS
|
||||
.lock()
|
||||
.register_driver(VIRTIO_MMIO_DRIVER.get().unwrap().clone());
|
||||
}
|
82
kernel/comps/virtio/src/transport/mmio/multiplex.rs
Normal file
82
kernel/comps/virtio/src/transport/mmio/multiplex.rs
Normal file
@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_frame::{
|
||||
io_mem::IoMem,
|
||||
sync::RwLock,
|
||||
trap::{IrqCallbackFunction, IrqLine, TrapFrame},
|
||||
};
|
||||
use aster_rights::{ReadOp, TRightSet, WriteOp};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
|
||||
/// Multiplexing Irqs. The two interrupt types (configuration space change and queue interrupt)
|
||||
/// of the virtio-mmio device share the same IRQ, so `MultiplexIrq` are used to distinguish them.
|
||||
/// Besides, virtio-mmio requires ack_interrupt after interrupt is handled.
|
||||
pub struct MultiplexIrq {
|
||||
irq: IrqLine,
|
||||
queue_callbacks: Vec<Box<IrqCallbackFunction>>,
|
||||
cfg_callbacks: Vec<Box<IrqCallbackFunction>>,
|
||||
interrupt_ack: SafePtr<u32, IoMem, TRightSet<WriteOp>>,
|
||||
interrupt_status: SafePtr<u32, IoMem, TRightSet<ReadOp>>,
|
||||
}
|
||||
|
||||
impl MultiplexIrq {
|
||||
pub fn new(
|
||||
irq: IrqLine,
|
||||
interrupt_ack: SafePtr<u32, IoMem, TRightSet<WriteOp>>,
|
||||
interrupt_status: SafePtr<u32, IoMem, TRightSet<ReadOp>>,
|
||||
) -> Arc<RwLock<Self>> {
|
||||
let irq = Arc::new(RwLock::new(Self {
|
||||
irq,
|
||||
queue_callbacks: Vec::new(),
|
||||
cfg_callbacks: Vec::new(),
|
||||
interrupt_ack,
|
||||
interrupt_status,
|
||||
}));
|
||||
// Holding a weak reference to prevent memory leakage due to
|
||||
// circular reference.
|
||||
let weak = Arc::downgrade(&irq);
|
||||
let mut lock = irq.write();
|
||||
let callback = move |trap_frame: &TrapFrame| {
|
||||
let Some(multiplex_irq) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let irq = multiplex_irq.read();
|
||||
let interrupt_status = irq.interrupt_status.read().unwrap();
|
||||
let callbacks = if interrupt_status & 0x01 == 1 {
|
||||
// Used buffer notification
|
||||
&irq.queue_callbacks
|
||||
} else {
|
||||
// Configuration Change Notification
|
||||
&irq.cfg_callbacks
|
||||
};
|
||||
for callback in callbacks.iter() {
|
||||
callback.call((trap_frame,));
|
||||
}
|
||||
irq.interrupt_ack.write(&interrupt_status).unwrap();
|
||||
};
|
||||
lock.irq.on_active(callback);
|
||||
drop(lock);
|
||||
irq
|
||||
}
|
||||
|
||||
pub fn register_queue_callback(&mut self, func: Box<IrqCallbackFunction>) {
|
||||
self.queue_callbacks.push(func);
|
||||
}
|
||||
|
||||
pub fn register_cfg_callback(&mut self, func: Box<IrqCallbackFunction>) {
|
||||
self.cfg_callbacks.push(func);
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for MultiplexIrq {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("MultiplexIrq")
|
||||
.field("irq", &self.irq)
|
||||
.field("interrupt_ack", &self.interrupt_ack)
|
||||
.field("interrupt_status", &self.interrupt_status)
|
||||
.finish()
|
||||
}
|
||||
}
|
136
kernel/comps/virtio/src/transport/mod.rs
Normal file
136
kernel/comps/virtio/src/transport/mod.rs
Normal file
@ -0,0 +1,136 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_frame::{io_mem::IoMem, trap::IrqCallbackFunction, vm::DmaCoherent};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
|
||||
use self::{mmio::virtio_mmio_init, pci::virtio_pci_init};
|
||||
use crate::{
|
||||
queue::{AvailRing, Descriptor, UsedRing},
|
||||
VirtioDeviceType,
|
||||
};
|
||||
|
||||
pub mod mmio;
|
||||
pub mod pci;
|
||||
|
||||
/// The transport of virtio device. Virtio device can use this transport to:
|
||||
/// 1. Set device status.
|
||||
/// 2. Negotiate features.
|
||||
/// 3. Access device config memory.
|
||||
/// 4. Config virtqueue.
|
||||
/// 5. Get the interrupt resources allocated to the device.
|
||||
pub trait VirtioTransport: Sync + Send + Debug {
|
||||
// ====================Device related APIs=======================
|
||||
|
||||
fn device_type(&self) -> VirtioDeviceType;
|
||||
|
||||
/// Get device features.
|
||||
fn device_features(&self) -> u64;
|
||||
|
||||
/// Set driver features.
|
||||
fn set_driver_features(&mut self, features: u64) -> Result<(), VirtioTransportError>;
|
||||
|
||||
/// Get device status.
|
||||
fn device_status(&self) -> DeviceStatus;
|
||||
|
||||
/// Set device status.
|
||||
fn set_device_status(&mut self, status: DeviceStatus) -> Result<(), VirtioTransportError>;
|
||||
|
||||
// Set to driver ok status
|
||||
fn finish_init(&mut self) {
|
||||
self.set_device_status(
|
||||
DeviceStatus::ACKNOWLEDGE
|
||||
| DeviceStatus::DRIVER
|
||||
| DeviceStatus::FEATURES_OK
|
||||
| DeviceStatus::DRIVER_OK,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Get access to the device config memory.
|
||||
fn device_config_memory(&self) -> IoMem;
|
||||
|
||||
// ====================Virtqueue related APIs====================
|
||||
|
||||
/// Get the total number of queues
|
||||
fn num_queues(&self) -> u16;
|
||||
|
||||
/// Set virtqueue information. Some transport may set other necessary information such as MSI-X vector in PCI transport.
|
||||
fn set_queue(
|
||||
&mut self,
|
||||
idx: u16,
|
||||
queue_size: u16,
|
||||
descriptor_ptr: &SafePtr<Descriptor, DmaCoherent>,
|
||||
avail_ring_ptr: &SafePtr<AvailRing, DmaCoherent>,
|
||||
used_ring_ptr: &SafePtr<UsedRing, DmaCoherent>,
|
||||
) -> Result<(), VirtioTransportError>;
|
||||
|
||||
/// The max queue size of one virtqueue.
|
||||
fn max_queue_size(&self, idx: u16) -> Result<u16, VirtioTransportError>;
|
||||
|
||||
/// Get notify pointer of a virtqueue. User should send notification (e.g. write 0 to the pointer)
|
||||
/// after it add buffers into the corresponding virtqueue.
|
||||
fn get_notify_ptr(&self, idx: u16) -> Result<SafePtr<u32, IoMem>, VirtioTransportError>;
|
||||
|
||||
fn is_legacy_version(&self) -> bool;
|
||||
|
||||
// ====================Device interrupt APIs=====================
|
||||
|
||||
/// Register queue interrupt callback. The transport will try to allocate single IRQ line if
|
||||
/// `single_interrupt` is set.
|
||||
fn register_queue_callback(
|
||||
&mut self,
|
||||
index: u16,
|
||||
func: Box<IrqCallbackFunction>,
|
||||
single_interrupt: bool,
|
||||
) -> Result<(), VirtioTransportError>;
|
||||
|
||||
/// Register configuration space change interrupt callback.
|
||||
fn register_cfg_callback(
|
||||
&mut self,
|
||||
func: Box<IrqCallbackFunction>,
|
||||
) -> Result<(), VirtioTransportError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum VirtioTransportError {
|
||||
DeviceStatusError,
|
||||
InvalidArgs,
|
||||
NotEnoughResources,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// The device status field.
|
||||
pub struct DeviceStatus: u8 {
|
||||
/// Indicates that the guest OS has found the device and recognized it
|
||||
/// as a valid virtio device.
|
||||
const ACKNOWLEDGE = 1;
|
||||
|
||||
/// Indicates that the guest OS knows how to drive the device.
|
||||
const DRIVER = 2;
|
||||
|
||||
/// Indicates that something went wrong in the guest, and it has given
|
||||
/// up on the device. This could be an internal error, or the driver
|
||||
/// didn’t like the device for some reason, or even a fatal error
|
||||
/// during device operation.
|
||||
const FAILED = 128;
|
||||
|
||||
/// Indicates that the driver has acknowledged all the features it
|
||||
/// understands, and feature negotiation is complete.
|
||||
const FEATURES_OK = 8;
|
||||
|
||||
/// Indicates that the driver is set up and ready to drive the device.
|
||||
const DRIVER_OK = 4;
|
||||
|
||||
/// Indicates that the device has experienced an error from which it
|
||||
/// can’t recover.
|
||||
const DEVICE_NEEDS_RESET = 64;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
virtio_pci_init();
|
||||
virtio_mmio_init();
|
||||
}
|
98
kernel/comps/virtio/src/transport/pci/capability.rs
Normal file
98
kernel/comps/virtio/src/transport/pci/capability.rs
Normal file
@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use aster_frame::bus::pci::{
|
||||
capability::vendor::CapabilityVndrData,
|
||||
cfg_space::{Bar, IoBar, MemoryBar},
|
||||
common_device::BarManager,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(u8)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum VirtioPciCpabilityType {
|
||||
CommonCfg = 1,
|
||||
NotifyCfg = 2,
|
||||
IsrCfg = 3,
|
||||
DeviceCfg = 4,
|
||||
PciCfg = 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VirtioPciCapabilityData {
|
||||
cfg_type: VirtioPciCpabilityType,
|
||||
offset: u32,
|
||||
length: u32,
|
||||
option: Option<u32>,
|
||||
memory_bar: Option<Arc<MemoryBar>>,
|
||||
io_bar: Option<Arc<IoBar>>,
|
||||
}
|
||||
|
||||
impl VirtioPciCapabilityData {
|
||||
pub fn memory_bar(&self) -> &Option<Arc<MemoryBar>> {
|
||||
&self.memory_bar
|
||||
}
|
||||
|
||||
pub fn io_bar(&self) -> &Option<Arc<IoBar>> {
|
||||
&self.io_bar
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> u32 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
pub fn length(&self) -> u32 {
|
||||
self.length
|
||||
}
|
||||
|
||||
pub fn typ(&self) -> VirtioPciCpabilityType {
|
||||
self.cfg_type.clone()
|
||||
}
|
||||
|
||||
pub fn option_value(&self) -> Option<u32> {
|
||||
self.option
|
||||
}
|
||||
|
||||
pub(super) fn new(bar_manager: &BarManager, vendor_cap: CapabilityVndrData) -> Self {
|
||||
let cfg_type = vendor_cap.read8(3).unwrap();
|
||||
let cfg_type = match cfg_type {
|
||||
1 => VirtioPciCpabilityType::CommonCfg,
|
||||
2 => VirtioPciCpabilityType::NotifyCfg,
|
||||
3 => VirtioPciCpabilityType::IsrCfg,
|
||||
4 => VirtioPciCpabilityType::DeviceCfg,
|
||||
5 => VirtioPciCpabilityType::PciCfg,
|
||||
_ => panic!("Unsupport virtio capability type:{:?}", cfg_type),
|
||||
};
|
||||
let bar = vendor_cap.read8(4).unwrap();
|
||||
let capability_length = vendor_cap.read8(2).unwrap();
|
||||
let offset = vendor_cap.read32(8).unwrap();
|
||||
let length = vendor_cap.read32(12).unwrap();
|
||||
let option = if capability_length > 0x10 {
|
||||
Some(vendor_cap.read32(16).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut io_bar = None;
|
||||
let mut memory_bar = None;
|
||||
if let Some(bar) = bar_manager.bar(bar) {
|
||||
match bar {
|
||||
Bar::Memory(memory) => {
|
||||
memory_bar = Some(memory);
|
||||
}
|
||||
Bar::Io(io) => {
|
||||
io_bar = Some(io);
|
||||
}
|
||||
}
|
||||
};
|
||||
Self {
|
||||
cfg_type,
|
||||
offset,
|
||||
length,
|
||||
option,
|
||||
memory_bar,
|
||||
io_bar,
|
||||
}
|
||||
}
|
||||
}
|
40
kernel/comps/virtio/src/transport/pci/common_cfg.rs
Normal file
40
kernel/comps/virtio/src/transport/pci/common_cfg.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_frame::io_mem::IoMem;
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use pod::Pod;
|
||||
|
||||
use super::capability::VirtioPciCapabilityData;
|
||||
use crate::transport::pci::capability::VirtioPciCpabilityType;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioPciCommonCfg {
|
||||
pub device_feature_select: u32,
|
||||
pub device_features: u32,
|
||||
pub driver_feature_select: u32,
|
||||
pub driver_features: u32,
|
||||
pub config_msix_vector: u16,
|
||||
pub num_queues: u16,
|
||||
pub device_status: u8,
|
||||
pub config_generation: u8,
|
||||
|
||||
pub queue_select: u16,
|
||||
pub queue_size: u16,
|
||||
pub queue_msix_vector: u16,
|
||||
pub queue_enable: u16,
|
||||
pub queue_notify_off: u16,
|
||||
pub queue_desc: u64,
|
||||
pub queue_driver: u64,
|
||||
pub queue_device: u64,
|
||||
}
|
||||
|
||||
impl VirtioPciCommonCfg {
|
||||
pub(super) fn new(cap: &VirtioPciCapabilityData) -> SafePtr<Self, IoMem> {
|
||||
debug_assert!(cap.typ() == VirtioPciCpabilityType::CommonCfg);
|
||||
SafePtr::new(
|
||||
cap.memory_bar().as_ref().unwrap().io_mem().clone(),
|
||||
cap.offset() as usize,
|
||||
)
|
||||
}
|
||||
}
|
350
kernel/comps/virtio/src/transport/pci/device.rs
Normal file
350
kernel/comps/virtio/src/transport/pci/device.rs
Normal file
@ -0,0 +1,350 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_frame::{
|
||||
bus::{
|
||||
pci::{
|
||||
bus::PciDevice, capability::CapabilityData, common_device::PciCommonDevice, PciDeviceId,
|
||||
},
|
||||
BusProbeError,
|
||||
},
|
||||
io_mem::IoMem,
|
||||
offset_of,
|
||||
trap::IrqCallbackFunction,
|
||||
vm::DmaCoherent,
|
||||
};
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use log::{info, warn};
|
||||
|
||||
use super::{common_cfg::VirtioPciCommonCfg, msix::VirtioMsixManager};
|
||||
use crate::{
|
||||
queue::{AvailRing, Descriptor, UsedRing},
|
||||
transport::{
|
||||
pci::capability::{VirtioPciCapabilityData, VirtioPciCpabilityType},
|
||||
DeviceStatus, VirtioTransport, VirtioTransportError,
|
||||
},
|
||||
VirtioDeviceType,
|
||||
};
|
||||
|
||||
pub struct VirtioPciNotify {
|
||||
offset_multiplier: u32,
|
||||
offset: u32,
|
||||
io_memory: IoMem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtioPciDevice {
|
||||
device_id: PciDeviceId,
|
||||
}
|
||||
|
||||
pub struct VirtioPciTransport {
|
||||
device_type: VirtioDeviceType,
|
||||
common_device: PciCommonDevice,
|
||||
common_cfg: SafePtr<VirtioPciCommonCfg, IoMem>,
|
||||
device_cfg: VirtioPciCapabilityData,
|
||||
notify: VirtioPciNotify,
|
||||
msix_manager: VirtioMsixManager,
|
||||
device: Arc<VirtioPciDevice>,
|
||||
}
|
||||
|
||||
impl PciDevice for VirtioPciDevice {
|
||||
fn device_id(&self) -> PciDeviceId {
|
||||
self.device_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VirtioPciTransport {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("PCIVirtioDevice")
|
||||
.field("common_device", &self.common_device)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioTransport for VirtioPciTransport {
|
||||
fn device_type(&self) -> VirtioDeviceType {
|
||||
self.device_type
|
||||
}
|
||||
|
||||
fn set_queue(
|
||||
&mut self,
|
||||
idx: u16,
|
||||
queue_size: u16,
|
||||
descriptor_ptr: &SafePtr<Descriptor, DmaCoherent>,
|
||||
avail_ring_ptr: &SafePtr<AvailRing, DmaCoherent>,
|
||||
used_ring_ptr: &SafePtr<UsedRing, DmaCoherent>,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
if idx >= self.num_queues() {
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
}
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_select)
|
||||
.write(&idx)
|
||||
.unwrap();
|
||||
debug_assert_eq!(
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_select)
|
||||
.read()
|
||||
.unwrap(),
|
||||
idx
|
||||
);
|
||||
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_size)
|
||||
.write(&queue_size)
|
||||
.unwrap();
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_desc)
|
||||
.write(&(descriptor_ptr.paddr() as u64))
|
||||
.unwrap();
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_driver)
|
||||
.write(&(avail_ring_ptr.paddr() as u64))
|
||||
.unwrap();
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_device)
|
||||
.write(&(used_ring_ptr.paddr() as u64))
|
||||
.unwrap();
|
||||
// Enable queue
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_enable)
|
||||
.write(&1u16)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_notify_ptr(&self, idx: u16) -> Result<SafePtr<u32, IoMem>, VirtioTransportError> {
|
||||
if idx >= self.num_queues() {
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
}
|
||||
Ok(SafePtr::new(
|
||||
self.notify.io_memory.clone(),
|
||||
(self.notify.offset + self.notify.offset_multiplier * idx as u32) as usize,
|
||||
))
|
||||
}
|
||||
|
||||
fn num_queues(&self) -> u16 {
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, num_queues)
|
||||
.read()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn device_config_memory(&self) -> IoMem {
|
||||
let mut memory = self
|
||||
.device_cfg
|
||||
.memory_bar()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.io_mem()
|
||||
.clone();
|
||||
let new_paddr = memory.paddr() + self.device_cfg.offset() as usize;
|
||||
memory
|
||||
.resize(new_paddr..(self.device_cfg.length() as usize + new_paddr))
|
||||
.unwrap();
|
||||
memory
|
||||
}
|
||||
|
||||
fn device_features(&self) -> u64 {
|
||||
// select low
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, device_feature_select)
|
||||
.write(&0u32)
|
||||
.unwrap();
|
||||
let device_feature_low = field_ptr!(&self.common_cfg, VirtioPciCommonCfg, device_features)
|
||||
.read()
|
||||
.unwrap();
|
||||
// select high
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, device_feature_select)
|
||||
.write(&1u32)
|
||||
.unwrap();
|
||||
let device_feature_high = field_ptr!(&self.common_cfg, VirtioPciCommonCfg, device_features)
|
||||
.read()
|
||||
.unwrap() as u64;
|
||||
device_feature_high << 32 | device_feature_low as u64
|
||||
}
|
||||
|
||||
fn set_driver_features(&mut self, features: u64) -> Result<(), VirtioTransportError> {
|
||||
let low = features as u32;
|
||||
let high = (features >> 32) as u32;
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, driver_feature_select)
|
||||
.write(&0u32)
|
||||
.unwrap();
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, driver_features)
|
||||
.write(&low)
|
||||
.unwrap();
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, driver_feature_select)
|
||||
.write(&1u32)
|
||||
.unwrap();
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, driver_features)
|
||||
.write(&high)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn device_status(&self) -> DeviceStatus {
|
||||
let status = field_ptr!(&self.common_cfg, VirtioPciCommonCfg, device_status)
|
||||
.read()
|
||||
.unwrap();
|
||||
DeviceStatus::from_bits(status).unwrap()
|
||||
}
|
||||
|
||||
fn set_device_status(&mut self, status: DeviceStatus) -> Result<(), VirtioTransportError> {
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, device_status)
|
||||
.write(&(status.bits()))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn max_queue_size(&self, idx: u16) -> Result<u16, crate::transport::VirtioTransportError> {
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_select)
|
||||
.write(&idx)
|
||||
.unwrap();
|
||||
debug_assert_eq!(
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_select)
|
||||
.read()
|
||||
.unwrap(),
|
||||
idx
|
||||
);
|
||||
|
||||
Ok(field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_size)
|
||||
.read()
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn register_queue_callback(
|
||||
&mut self,
|
||||
index: u16,
|
||||
func: Box<IrqCallbackFunction>,
|
||||
single_interrupt: bool,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
if index >= self.num_queues() {
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
}
|
||||
let (vector, irq) = if single_interrupt {
|
||||
self.msix_manager
|
||||
.pop_unused_irq()
|
||||
.ok_or(VirtioTransportError::NotEnoughResources)?
|
||||
} else {
|
||||
self.msix_manager.shared_interrupt_irq()
|
||||
};
|
||||
irq.on_active(func);
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_select)
|
||||
.write(&index)
|
||||
.unwrap();
|
||||
debug_assert_eq!(
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_select)
|
||||
.read()
|
||||
.unwrap(),
|
||||
index
|
||||
);
|
||||
field_ptr!(&self.common_cfg, VirtioPciCommonCfg, queue_msix_vector)
|
||||
.write(&vector)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_cfg_callback(
|
||||
&mut self,
|
||||
func: Box<IrqCallbackFunction>,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
let (_, irq) = self.msix_manager.config_msix_irq();
|
||||
irq.on_active(func);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_legacy_version(&self) -> bool {
|
||||
// TODO: Support legacy version
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioPciTransport {
|
||||
pub(super) fn pci_device(&self) -> &Arc<VirtioPciDevice> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub(super) fn new(
|
||||
common_device: PciCommonDevice,
|
||||
) -> Result<Self, (BusProbeError, PciCommonDevice)> {
|
||||
let device_type = match common_device.device_id().device_id {
|
||||
0x1000 => VirtioDeviceType::Network,
|
||||
0x1001 => VirtioDeviceType::Block,
|
||||
0x1002 => VirtioDeviceType::TraditionalMemoryBalloon,
|
||||
0x1003 => VirtioDeviceType::Console,
|
||||
0x1004 => VirtioDeviceType::ScsiHost,
|
||||
0x1005 => VirtioDeviceType::Entropy,
|
||||
0x1009 => VirtioDeviceType::Transport9P,
|
||||
id => {
|
||||
if id <= 0x1040 {
|
||||
warn!(
|
||||
"Unrecognized virtio-pci device id:{:x?}",
|
||||
common_device.device_id().device_id
|
||||
);
|
||||
return Err((BusProbeError::ConfigurationSpaceError, common_device));
|
||||
}
|
||||
let id = id - 0x1040;
|
||||
match VirtioDeviceType::try_from(id as u8) {
|
||||
Ok(device) => device,
|
||||
Err(_) => {
|
||||
warn!(
|
||||
"Unrecognized virtio-pci device id:{:x?}",
|
||||
common_device.device_id().device_id
|
||||
);
|
||||
return Err((BusProbeError::ConfigurationSpaceError, common_device));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("[Virtio]: Found device:{:?}", device_type);
|
||||
|
||||
let mut msix = None;
|
||||
let mut notify = None;
|
||||
let mut common_cfg = None;
|
||||
let mut device_cfg = None;
|
||||
for cap in common_device.capabilities().iter() {
|
||||
match cap.capability_data() {
|
||||
CapabilityData::Vndr(vendor) => {
|
||||
let data = VirtioPciCapabilityData::new(common_device.bar_manager(), *vendor);
|
||||
match data.typ() {
|
||||
VirtioPciCpabilityType::CommonCfg => {
|
||||
common_cfg = Some(VirtioPciCommonCfg::new(&data));
|
||||
}
|
||||
VirtioPciCpabilityType::NotifyCfg => {
|
||||
notify = Some(VirtioPciNotify {
|
||||
offset_multiplier: data.option_value().unwrap(),
|
||||
offset: data.offset(),
|
||||
io_memory: data.memory_bar().as_ref().unwrap().io_mem().clone(),
|
||||
});
|
||||
}
|
||||
VirtioPciCpabilityType::IsrCfg => {}
|
||||
VirtioPciCpabilityType::DeviceCfg => {
|
||||
device_cfg = Some(data);
|
||||
}
|
||||
VirtioPciCpabilityType::PciCfg => {}
|
||||
}
|
||||
}
|
||||
CapabilityData::Msix(data) => {
|
||||
msix = Some(data.clone());
|
||||
}
|
||||
CapabilityData::Unknown(id) => {
|
||||
panic!("unknown capability: {}", id)
|
||||
}
|
||||
_ => {
|
||||
panic!("PCI Virtio device should not have other type of capability")
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Support interrupt without MSI-X
|
||||
let msix = msix.unwrap();
|
||||
let notify = notify.unwrap();
|
||||
let common_cfg = common_cfg.unwrap();
|
||||
let device_cfg = device_cfg.unwrap();
|
||||
let msix_manager = VirtioMsixManager::new(msix);
|
||||
let device_id = *common_device.device_id();
|
||||
Ok(Self {
|
||||
common_device,
|
||||
common_cfg,
|
||||
device_cfg,
|
||||
notify,
|
||||
msix_manager,
|
||||
device_type,
|
||||
device: Arc::new(VirtioPciDevice { device_id }),
|
||||
})
|
||||
}
|
||||
}
|
53
kernel/comps/virtio/src/transport/pci/driver.rs
Normal file
53
kernel/comps/virtio/src/transport/pci/driver.rs
Normal file
@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
|
||||
use aster_frame::{
|
||||
bus::{
|
||||
pci::{
|
||||
bus::{PciDevice, PciDriver},
|
||||
common_device::PciCommonDevice,
|
||||
},
|
||||
BusProbeError,
|
||||
},
|
||||
sync::SpinLock,
|
||||
};
|
||||
|
||||
use super::device::VirtioPciTransport;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtioPciDriver {
|
||||
devices: SpinLock<Vec<VirtioPciTransport>>,
|
||||
}
|
||||
|
||||
impl VirtioPciDriver {
|
||||
pub fn num_devices(&self) -> usize {
|
||||
self.devices.lock().len()
|
||||
}
|
||||
|
||||
pub fn pop_device_transport(&self) -> Option<VirtioPciTransport> {
|
||||
self.devices.lock().pop()
|
||||
}
|
||||
|
||||
pub(super) fn new() -> Self {
|
||||
VirtioPciDriver {
|
||||
devices: SpinLock::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDriver for VirtioPciDriver {
|
||||
fn probe(
|
||||
&self,
|
||||
device: PciCommonDevice,
|
||||
) -> Result<Arc<dyn PciDevice>, (BusProbeError, PciCommonDevice)> {
|
||||
const VIRTIO_DEVICE_VENDOR_ID: u16 = 0x1af4;
|
||||
if device.device_id().vendor_id != VIRTIO_DEVICE_VENDOR_ID {
|
||||
return Err((BusProbeError::DeviceNotMatch, device));
|
||||
}
|
||||
let transport = VirtioPciTransport::new(device)?;
|
||||
let device = transport.pci_device().clone();
|
||||
self.devices.lock().push(transport);
|
||||
Ok(device)
|
||||
}
|
||||
}
|
22
kernel/comps/virtio/src/transport/pci/mod.rs
Normal file
22
kernel/comps/virtio/src/transport/pci/mod.rs
Normal file
@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod capability;
|
||||
pub mod common_cfg;
|
||||
pub mod device;
|
||||
pub mod driver;
|
||||
pub(super) mod msix;
|
||||
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use aster_frame::bus::pci::PCI_BUS;
|
||||
use spin::Once;
|
||||
|
||||
use self::driver::VirtioPciDriver;
|
||||
|
||||
pub static VIRTIO_PCI_DRIVER: Once<Arc<VirtioPciDriver>> = Once::new();
|
||||
pub fn virtio_pci_init() {
|
||||
VIRTIO_PCI_DRIVER.call_once(|| Arc::new(VirtioPciDriver::new()));
|
||||
PCI_BUS
|
||||
.lock()
|
||||
.register_driver(VIRTIO_PCI_DRIVER.get().unwrap().clone());
|
||||
}
|
65
kernel/comps/virtio/src/transport/pci/msix.rs
Normal file
65
kernel/comps/virtio/src/transport/pci/msix.rs
Normal file
@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use aster_frame::{bus::pci::capability::msix::CapabilityMsixData, trap::IrqLine};
|
||||
|
||||
pub struct VirtioMsixManager {
|
||||
config_msix_vector: u16,
|
||||
/// Shared interrupt vector used by queue.
|
||||
shared_interrupt_vector: u16,
|
||||
/// The MSI-X vectors allocated to queue interrupt except `shared_interrupt_vector`. All the
|
||||
/// vector are considered to be occupied by only one queue.
|
||||
unused_msix_vectors: Vec<u16>,
|
||||
/// Used MSI-X vectors.
|
||||
used_msix_vectors: Vec<u16>,
|
||||
msix: CapabilityMsixData,
|
||||
}
|
||||
|
||||
impl VirtioMsixManager {
|
||||
pub fn new(mut msix: CapabilityMsixData) -> Self {
|
||||
let mut msix_vector_list: Vec<u16> = (0..msix.table_size()).collect();
|
||||
for i in msix_vector_list.iter() {
|
||||
let irq = aster_frame::trap::IrqLine::alloc().unwrap();
|
||||
msix.set_interrupt_vector(irq, *i);
|
||||
}
|
||||
let config_msix_vector = msix_vector_list.pop().unwrap();
|
||||
let shared_interrupt_vector = msix_vector_list.pop().unwrap();
|
||||
Self {
|
||||
config_msix_vector,
|
||||
unused_msix_vectors: msix_vector_list,
|
||||
msix,
|
||||
shared_interrupt_vector,
|
||||
used_msix_vectors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get config space change MSI-X IRQ, this function will return the MSI-X vector and corresponding IRQ.
|
||||
pub fn config_msix_irq(&mut self) -> (u16, &mut IrqLine) {
|
||||
(
|
||||
self.config_msix_vector,
|
||||
self.msix.irq_mut(self.config_msix_vector as usize).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get shared interrupt IRQ used by virtqueue. If a virtqueue will not send interrupt frequently.
|
||||
/// Then this virtqueue should use shared interrupt IRQ.
|
||||
/// This function will return the MSI-X vector and corresponding IRQ.
|
||||
pub fn shared_interrupt_irq(&mut self) -> (u16, &mut IrqLine) {
|
||||
(
|
||||
self.shared_interrupt_vector,
|
||||
self.msix
|
||||
.irq_mut(self.shared_interrupt_vector as usize)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Pop unused vector. If a virtqueue will send interrupt frequently.
|
||||
/// Then this virtqueue should use the single IRQ that this function provides.
|
||||
/// this function will return the MSI-X vector and corresponding IRQ.
|
||||
pub fn pop_unused_irq(&mut self) -> Option<(u16, &mut IrqLine)> {
|
||||
let vector = self.unused_msix_vectors.pop()?;
|
||||
self.used_msix_vectors.push(vector);
|
||||
Some((vector, self.msix.irq_mut(vector as usize).unwrap()))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user