mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-19 20:46:35 +00:00
Add virtio legacy interface
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
495c93c2ad
commit
81898362b6
@ -1,6 +1,13 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, collections::BTreeMap, string::String, sync::Arc, vec, vec::Vec};
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
collections::BTreeMap,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
use core::{fmt::Debug, hint::spin_loop, mem::size_of};
|
||||
|
||||
use aster_block::{
|
||||
@ -8,11 +15,9 @@ use aster_block::{
|
||||
request_queue::{BioRequest, BioRequestSingleQueue},
|
||||
BlockDeviceMeta,
|
||||
};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use id_alloc::IdAlloc;
|
||||
use log::info;
|
||||
use log::{debug, info};
|
||||
use ostd::{
|
||||
io_mem::IoMem,
|
||||
mm::{DmaDirection, DmaStream, DmaStreamSlice, FrameAllocOptions, VmIo},
|
||||
sync::SpinLock,
|
||||
trap::TrapFrame,
|
||||
@ -26,7 +31,7 @@ use crate::{
|
||||
VirtioDeviceError,
|
||||
},
|
||||
queue::VirtQueue,
|
||||
transport::VirtioTransport,
|
||||
transport::{ConfigManager, VirtioTransport},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -39,8 +44,14 @@ pub struct BlockDevice {
|
||||
impl BlockDevice {
|
||||
/// Creates a new VirtIO-Block driver and registers it.
|
||||
pub(crate) fn init(transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let is_legacy = transport.is_legacy_version();
|
||||
let device = DeviceInner::init(transport)?;
|
||||
let device_id = device.request_device_id();
|
||||
let device_id = if is_legacy {
|
||||
// FIXME: legacy device do not support `GetId` request.
|
||||
"legacy_blk".to_string()
|
||||
} else {
|
||||
device.request_device_id()
|
||||
};
|
||||
|
||||
let block_device = Arc::new(Self {
|
||||
device,
|
||||
@ -70,9 +81,9 @@ impl BlockDevice {
|
||||
|
||||
/// Negotiate features for the device specified bits 0~23
|
||||
pub(crate) fn negotiate_features(features: u64) -> u64 {
|
||||
let feature = BlockFeatures::from_bits(features).unwrap();
|
||||
let support_features = BlockFeatures::from_bits(features).unwrap();
|
||||
(feature & support_features).bits
|
||||
let mut support_features = BlockFeatures::from_bits_truncate(features);
|
||||
support_features.remove(BlockFeatures::MQ);
|
||||
support_features.bits
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,14 +95,14 @@ impl aster_block::BlockDevice for BlockDevice {
|
||||
fn metadata(&self) -> BlockDeviceMeta {
|
||||
BlockDeviceMeta {
|
||||
max_nr_segments_per_bio: self.queue.max_nr_segments_per_bio(),
|
||||
nr_sectors: VirtioBlockConfig::read_capacity_sectors(&self.device.config).unwrap(),
|
||||
nr_sectors: self.device.config_manager.capacity_sectors(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DeviceInner {
|
||||
config: SafePtr<VirtioBlockConfig, IoMem>,
|
||||
config_manager: ConfigManager<VirtioBlockConfig>,
|
||||
features: VirtioBlockFeature,
|
||||
queue: SpinLock<VirtQueue>,
|
||||
transport: SpinLock<Box<dyn VirtioTransport>>,
|
||||
@ -106,9 +117,10 @@ impl DeviceInner {
|
||||
|
||||
/// Creates and inits the device.
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<Arc<Self>, VirtioDeviceError> {
|
||||
let config = VirtioBlockConfig::new(transport.as_mut());
|
||||
let config_manager = VirtioBlockConfig::new_manager(transport.as_ref());
|
||||
debug!("virio_blk_config = {:?}", config_manager.read_config());
|
||||
assert_eq!(
|
||||
VirtioBlockConfig::read_block_size(&config).unwrap(),
|
||||
config_manager.block_size(),
|
||||
VirtioBlockConfig::sector_size(),
|
||||
"currently not support customized device logical block size"
|
||||
);
|
||||
@ -138,7 +150,7 @@ impl DeviceInner {
|
||||
assert!(Self::QUEUE_SIZE as usize * RESP_SIZE <= block_responses.nbytes());
|
||||
|
||||
let device = Arc::new(Self {
|
||||
config,
|
||||
config_manager,
|
||||
features,
|
||||
queue: SpinLock::new(queue),
|
||||
transport: SpinLock::new(transport),
|
||||
|
@ -2,13 +2,15 @@
|
||||
|
||||
pub mod device;
|
||||
|
||||
use core::mem::offset_of;
|
||||
|
||||
use aster_block::SECTOR_SIZE;
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use bitflags::bitflags;
|
||||
use int_to_c_enum::TryFromInt;
|
||||
use ostd::{io_mem::IoMem, Pod};
|
||||
use ostd::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
use crate::transport::{ConfigManager, VirtioTransport};
|
||||
|
||||
pub static DEVICE_NAME: &str = "Virtio-Block";
|
||||
|
||||
@ -73,7 +75,9 @@ pub struct VirtioBlockConfig {
|
||||
topology: VirtioBlockTopology,
|
||||
/// Writeback mode.
|
||||
writeback: u8,
|
||||
unused0: [u8; 3],
|
||||
unused0: u8,
|
||||
/// The number of virtqueues.
|
||||
num_queues: u16,
|
||||
/// The maximum discard sectors for one segment.
|
||||
max_discard_sectors: u32,
|
||||
/// The maximum number of discard segments in a discard command.
|
||||
@ -118,27 +122,78 @@ pub struct VirtioBlockFeature {
|
||||
}
|
||||
|
||||
impl VirtioBlockConfig {
|
||||
pub(self) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
pub(self) fn new_manager(transport: &dyn VirtioTransport) -> ConfigManager<Self> {
|
||||
let safe_ptr = transport
|
||||
.device_config_mem()
|
||||
.map(|mem| SafePtr::new(mem, 0));
|
||||
let bar_space = transport.device_config_bar();
|
||||
|
||||
ConfigManager::new(safe_ptr, bar_space)
|
||||
}
|
||||
|
||||
pub(self) const fn sector_size() -> usize {
|
||||
SECTOR_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
pub(self) fn read_block_size(this: &SafePtr<Self, IoMem>) -> ostd::prelude::Result<usize> {
|
||||
field_ptr!(this, Self, blk_size)
|
||||
.read_once()
|
||||
.map(|val| val as usize)
|
||||
impl ConfigManager<VirtioBlockConfig> {
|
||||
pub(super) fn read_config(&self) -> VirtioBlockConfig {
|
||||
let mut blk_config = VirtioBlockConfig::new_uninit();
|
||||
// Only following fields are defined in legacy interface.
|
||||
let cap_low = self
|
||||
.read_once::<u32>(offset_of!(VirtioBlockConfig, capacity))
|
||||
.unwrap() as u64;
|
||||
let cap_high = self
|
||||
.read_once::<u32>(offset_of!(VirtioBlockConfig, capacity) + 4)
|
||||
.unwrap() as u64;
|
||||
blk_config.capacity = cap_high << 32 | cap_low;
|
||||
blk_config.size_max = self
|
||||
.read_once::<u32>(offset_of!(VirtioBlockConfig, size_max))
|
||||
.unwrap();
|
||||
blk_config.seg_max = self
|
||||
.read_once::<u32>(offset_of!(VirtioBlockConfig, seg_max))
|
||||
.unwrap();
|
||||
blk_config.geometry.cylinders = self
|
||||
.read_once::<u16>(
|
||||
offset_of!(VirtioBlockConfig, geometry)
|
||||
+ offset_of!(VirtioBlockGeometry, cylinders),
|
||||
)
|
||||
.unwrap();
|
||||
blk_config.geometry.heads = self
|
||||
.read_once::<u8>(
|
||||
offset_of!(VirtioBlockConfig, geometry) + offset_of!(VirtioBlockGeometry, heads),
|
||||
)
|
||||
.unwrap();
|
||||
blk_config.geometry.sectors = self
|
||||
.read_once::<u8>(
|
||||
offset_of!(VirtioBlockConfig, geometry) + offset_of!(VirtioBlockGeometry, sectors),
|
||||
)
|
||||
.unwrap();
|
||||
blk_config.blk_size = self
|
||||
.read_once::<u32>(offset_of!(VirtioBlockConfig, blk_size))
|
||||
.unwrap();
|
||||
|
||||
if self.is_modern() {
|
||||
// TODO: read more field if modern interface exists.
|
||||
}
|
||||
|
||||
blk_config
|
||||
}
|
||||
|
||||
pub(self) fn read_capacity_sectors(
|
||||
this: &SafePtr<Self, IoMem>,
|
||||
) -> ostd::prelude::Result<usize> {
|
||||
field_ptr!(this, Self, capacity)
|
||||
.read_once()
|
||||
.map(|val| val as usize)
|
||||
pub(self) fn block_size(&self) -> usize {
|
||||
self.read_once::<u32>(offset_of!(VirtioBlockConfig, blk_size))
|
||||
.unwrap() as usize
|
||||
}
|
||||
|
||||
pub(self) fn capacity_sectors(&self) -> usize {
|
||||
let cap_low = self
|
||||
.read_once::<u32>(offset_of!(VirtioBlockConfig, capacity))
|
||||
.unwrap() as usize;
|
||||
let cap_high = self
|
||||
.read_once::<u32>(offset_of!(VirtioBlockConfig, capacity) + 4)
|
||||
.unwrap() as usize;
|
||||
|
||||
cap_high << 32 | cap_low
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use ostd::{io_mem::IoMem, Pod};
|
||||
use core::mem::offset_of;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use ostd::Pod;
|
||||
|
||||
use crate::transport::{ConfigManager, VirtioTransport};
|
||||
|
||||
bitflags::bitflags! {
|
||||
pub struct ConsoleFeatures: u64{
|
||||
@ -22,14 +24,49 @@ bitflags::bitflags! {
|
||||
#[repr(C)]
|
||||
pub struct VirtioConsoleConfig {
|
||||
pub cols: u16,
|
||||
pub row: u16,
|
||||
pub rows: u16,
|
||||
pub max_nr_ports: u32,
|
||||
pub emerg_wr: u32,
|
||||
}
|
||||
|
||||
impl VirtioConsoleConfig {
|
||||
pub(super) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
pub(super) fn new_manager(transport: &dyn VirtioTransport) -> ConfigManager<Self> {
|
||||
let safe_ptr = transport
|
||||
.device_config_mem()
|
||||
.map(|mem| SafePtr::new(mem, 0));
|
||||
let bar_space = transport.device_config_bar();
|
||||
ConfigManager::new(safe_ptr, bar_space)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigManager<VirtioConsoleConfig> {
|
||||
pub(super) fn read_config(&self) -> VirtioConsoleConfig {
|
||||
let mut console_config = VirtioConsoleConfig::new_uninit();
|
||||
// Only following fields are defined in legacy interface.
|
||||
console_config.cols = self
|
||||
.read_once::<u16>(offset_of!(VirtioConsoleConfig, cols))
|
||||
.unwrap();
|
||||
console_config.rows = self
|
||||
.read_once::<u16>(offset_of!(VirtioConsoleConfig, rows))
|
||||
.unwrap();
|
||||
console_config.max_nr_ports = self
|
||||
.read_once::<u32>(offset_of!(VirtioConsoleConfig, max_nr_ports))
|
||||
.unwrap();
|
||||
|
||||
console_config
|
||||
}
|
||||
|
||||
/// Performs an emergency write.
|
||||
///
|
||||
/// According to the VirtIO spec 5.3.4:
|
||||
///
|
||||
/// If `VIRTIO_CONSOLE_F_EMERG_WRITE` is supported then the driver can
|
||||
/// use emergency write to output a single character without initializing
|
||||
/// virtio queues, or even acknowledging the feature.
|
||||
pub(super) fn emerg_write(&self, value: u32) {
|
||||
if self.is_modern() {
|
||||
self.write_once(offset_of!(VirtioConsoleConfig, emerg_wr), value)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,8 @@ use alloc::{boxed::Box, fmt::Debug, string::ToString, sync::Arc, vec::Vec};
|
||||
use core::hint::spin_loop;
|
||||
|
||||
use aster_console::{AnyConsoleDevice, ConsoleCallback};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use log::debug;
|
||||
use ostd::{
|
||||
io_mem::IoMem,
|
||||
mm::{DmaDirection, DmaStream, DmaStreamSlice, FrameAllocOptions, VmReader},
|
||||
sync::{LocalIrqDisabled, RwLock, SpinLock},
|
||||
trap::TrapFrame,
|
||||
@ -17,11 +15,11 @@ use super::{config::VirtioConsoleConfig, DEVICE_NAME};
|
||||
use crate::{
|
||||
device::{console::config::ConsoleFeatures, VirtioDeviceError},
|
||||
queue::VirtQueue,
|
||||
transport::VirtioTransport,
|
||||
transport::{ConfigManager, VirtioTransport},
|
||||
};
|
||||
|
||||
pub struct ConsoleDevice {
|
||||
config: SafePtr<VirtioConsoleConfig, IoMem>,
|
||||
config_manager: ConfigManager<VirtioConsoleConfig>,
|
||||
transport: SpinLock<Box<dyn VirtioTransport>>,
|
||||
receive_queue: SpinLock<VirtQueue>,
|
||||
transmit_queue: SpinLock<VirtQueue>,
|
||||
@ -61,7 +59,7 @@ impl AnyConsoleDevice for ConsoleDevice {
|
||||
impl Debug for ConsoleDevice {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("ConsoleDevice")
|
||||
.field("config", &self.config)
|
||||
.field("config", &self.config_manager.read_config())
|
||||
.field("transport", &self.transport)
|
||||
.field("receive_queue", &self.receive_queue)
|
||||
.field("transmit_queue", &self.transmit_queue)
|
||||
@ -78,7 +76,9 @@ impl ConsoleDevice {
|
||||
}
|
||||
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let config = VirtioConsoleConfig::new(transport.as_ref());
|
||||
let config_manager = VirtioConsoleConfig::new_manager(transport.as_ref());
|
||||
debug!("virtio_console_config = {:?}", config_manager.read_config());
|
||||
|
||||
const RECV0_QUEUE_INDEX: u16 = 0;
|
||||
const TRANSMIT0_QUEUE_INDEX: u16 = 1;
|
||||
let receive_queue =
|
||||
@ -97,7 +97,7 @@ impl ConsoleDevice {
|
||||
};
|
||||
|
||||
let device = Arc::new(Self {
|
||||
config,
|
||||
config_manager,
|
||||
transport: SpinLock::new(transport),
|
||||
receive_queue,
|
||||
transmit_queue,
|
||||
|
@ -77,7 +77,7 @@ pub struct VirtioInputConfig {
|
||||
|
||||
impl VirtioInputConfig {
|
||||
pub(self) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
let memory = transport.device_config_mem().unwrap();
|
||||
SafePtr::new(memory, 0)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_network::EthernetAddr;
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use bitflags::bitflags;
|
||||
use ostd::{io_mem::IoMem, offset_of, Pod};
|
||||
use core::mem::offset_of;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
use aster_network::EthernetAddr;
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use bitflags::bitflags;
|
||||
use ostd::Pod;
|
||||
|
||||
use crate::transport::{ConfigManager, VirtioTransport};
|
||||
|
||||
bitflags! {
|
||||
/// Virtio Net Feature bits.
|
||||
@ -77,38 +79,55 @@ pub struct VirtioNetConfig {
|
||||
}
|
||||
|
||||
impl VirtioNetConfig {
|
||||
pub(super) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
}
|
||||
|
||||
pub(super) fn read(this: &SafePtr<Self, IoMem>) -> ostd::prelude::Result<Self> {
|
||||
// TODO: Add a general API to read this byte-by-byte.
|
||||
let mac_data = {
|
||||
let mut mac_data: [u8; 6] = [0; 6];
|
||||
let mut mac_ptr = field_ptr!(this, Self, mac).cast::<u8>();
|
||||
for byte in &mut mac_data {
|
||||
*byte = mac_ptr.read_once().unwrap();
|
||||
mac_ptr.byte_add(1);
|
||||
}
|
||||
mac_data
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
mac: aster_network::EthernetAddr(mac_data),
|
||||
status: field_ptr!(this, Self, status).read_once()?,
|
||||
max_virtqueue_pairs: field_ptr!(this, Self, max_virtqueue_pairs).read_once()?,
|
||||
mtu: field_ptr!(this, Self, mtu).read_once()?,
|
||||
speed: field_ptr!(this, Self, speed).read_once()?,
|
||||
duplex: field_ptr!(this, Self, duplex).read_once()?,
|
||||
rss_max_key_size: field_ptr!(this, Self, rss_max_key_size).read_once()?,
|
||||
rss_max_indirection_table_length: field_ptr!(
|
||||
this,
|
||||
Self,
|
||||
rss_max_indirection_table_length
|
||||
)
|
||||
.read_once()?,
|
||||
supported_hash_types: field_ptr!(this, Self, supported_hash_types).read_once()?,
|
||||
})
|
||||
pub(super) fn new_manager(transport: &dyn VirtioTransport) -> ConfigManager<Self> {
|
||||
let safe_ptr = transport
|
||||
.device_config_mem()
|
||||
.map(|mem| SafePtr::new(mem, 0));
|
||||
let bar_space = transport.device_config_bar();
|
||||
ConfigManager::new(safe_ptr, bar_space)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigManager<VirtioNetConfig> {
|
||||
pub(super) fn read_config(&self) -> VirtioNetConfig {
|
||||
let mut net_config = VirtioNetConfig::new_uninit();
|
||||
// Only following fields are defined in legacy interface.
|
||||
for i in 0..6 {
|
||||
net_config.mac.0[i] = self
|
||||
.read_once::<u8>(offset_of!(VirtioNetConfig, mac) + i)
|
||||
.unwrap();
|
||||
}
|
||||
net_config.status.bits = self
|
||||
.read_once::<u16>(offset_of!(VirtioNetConfig, status))
|
||||
.unwrap();
|
||||
|
||||
if self.is_modern() {
|
||||
net_config.max_virtqueue_pairs = self
|
||||
.read_once::<u16>(offset_of!(VirtioNetConfig, max_virtqueue_pairs))
|
||||
.unwrap();
|
||||
net_config.mtu = self
|
||||
.read_once::<u16>(offset_of!(VirtioNetConfig, mtu))
|
||||
.unwrap();
|
||||
net_config.speed = self
|
||||
.read_once::<u32>(offset_of!(VirtioNetConfig, speed))
|
||||
.unwrap();
|
||||
net_config.duplex = self
|
||||
.read_once::<u8>(offset_of!(VirtioNetConfig, duplex))
|
||||
.unwrap();
|
||||
net_config.rss_max_key_size = self
|
||||
.read_once::<u8>(offset_of!(VirtioNetConfig, rss_max_key_size))
|
||||
.unwrap();
|
||||
net_config.rss_max_indirection_table_length = self
|
||||
.read_once::<u16>(offset_of!(
|
||||
VirtioNetConfig,
|
||||
rss_max_indirection_table_length
|
||||
))
|
||||
.unwrap();
|
||||
net_config.supported_hash_types = self
|
||||
.read_once::<u32>(offset_of!(VirtioNetConfig, supported_hash_types))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
net_config
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ use super::{config::VirtioNetConfig, header::VirtioNetHdr};
|
||||
use crate::{
|
||||
device::{network::config::NetworkFeatures, VirtioDeviceError},
|
||||
queue::{QueueError, VirtQueue},
|
||||
transport::VirtioTransport,
|
||||
transport::{ConfigManager, VirtioTransport},
|
||||
};
|
||||
|
||||
pub struct NetworkDevice {
|
||||
config: VirtioNetConfig,
|
||||
config_manager: ConfigManager<VirtioNetConfig>,
|
||||
// For smoltcp use
|
||||
caps: DeviceCapabilities,
|
||||
mac_addr: EthernetAddr,
|
||||
@ -57,16 +57,15 @@ impl NetworkDevice {
|
||||
}
|
||||
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let virtio_net_config = VirtioNetConfig::new(transport.as_mut());
|
||||
let config_manager = VirtioNetConfig::new_manager(transport.as_ref());
|
||||
let config = config_manager.read_config();
|
||||
debug!("virtio_net_config = {:?}", config);
|
||||
let mac_addr = config.mac;
|
||||
let features = NetworkFeatures::from_bits_truncate(Self::negotiate_features(
|
||||
transport.read_device_features(),
|
||||
));
|
||||
debug!("virtio_net_config = {:?}", virtio_net_config);
|
||||
debug!("features = {:?}", features);
|
||||
|
||||
let config = VirtioNetConfig::read(&virtio_net_config).unwrap();
|
||||
let mac_addr = config.mac;
|
||||
debug!("mac addr = {:x?}, status = {:?}", mac_addr, config.status);
|
||||
let caps = init_caps(&features, &config);
|
||||
|
||||
let mut send_queue = VirtQueue::new(QUEUE_SEND, QUEUE_SIZE, transport.as_mut())
|
||||
@ -94,7 +93,7 @@ impl NetworkDevice {
|
||||
}
|
||||
|
||||
let mut device = Self {
|
||||
config,
|
||||
config_manager,
|
||||
caps,
|
||||
mac_addr,
|
||||
send_queue,
|
||||
@ -294,7 +293,7 @@ impl AnyNetworkDevice for NetworkDevice {
|
||||
impl Debug for NetworkDevice {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("NetworkDevice")
|
||||
.field("config", &self.config)
|
||||
.field("config", &self.config_manager.read_config())
|
||||
.field("mac_addr", &self.mac_addr)
|
||||
.field("send_queue", &self.send_queue)
|
||||
.field("recv_queue", &self.recv_queue)
|
||||
|
@ -34,7 +34,7 @@ pub struct VirtioVsockConfig {
|
||||
|
||||
impl VirtioVsockConfig {
|
||||
pub(crate) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
let memory = transport.device_config_mem().unwrap();
|
||||
SafePtr::new(memory, 0)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use core::hint::spin_loop;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use component::{init_component, ComponentInitError};
|
||||
@ -43,6 +44,10 @@ fn virtio_component_init() -> Result<(), ComponentInitError> {
|
||||
transport
|
||||
.write_device_status(DeviceStatus::empty())
|
||||
.unwrap();
|
||||
while transport.read_device_status() != DeviceStatus::empty() {
|
||||
spin_loop();
|
||||
}
|
||||
|
||||
// Set to acknowledge
|
||||
transport
|
||||
.write_device_status(DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER)
|
||||
@ -50,12 +55,13 @@ fn virtio_component_init() -> Result<(), ComponentInitError> {
|
||||
// negotiate features
|
||||
negotiate_features(&mut transport);
|
||||
|
||||
// change to features ok status
|
||||
transport
|
||||
.write_device_status(
|
||||
DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::FEATURES_OK,
|
||||
)
|
||||
.unwrap();
|
||||
if !transport.is_legacy_version() {
|
||||
// change to features ok status
|
||||
let status =
|
||||
DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::FEATURES_OK;
|
||||
transport.write_device_status(status).unwrap();
|
||||
}
|
||||
|
||||
let device_type = transport.device_type();
|
||||
let res = match transport.device_type() {
|
||||
VirtioDeviceType::Block => BlockDevice::init(transport),
|
||||
@ -80,7 +86,7 @@ fn virtio_component_init() -> Result<(), ComponentInitError> {
|
||||
|
||||
fn pop_device_transport() -> Option<Box<dyn VirtioTransport>> {
|
||||
if let Some(device) = VIRTIO_PCI_DRIVER.get().unwrap().pop_device_transport() {
|
||||
return Some(Box::new(device));
|
||||
return Some(device);
|
||||
}
|
||||
if let Some(device) = VIRTIO_MMIO_DRIVER.get().unwrap().pop_device_transport() {
|
||||
return Some(Box::new(device));
|
||||
|
@ -13,12 +13,14 @@ use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use bitflags::bitflags;
|
||||
use log::debug;
|
||||
use ostd::{
|
||||
io_mem::IoMem,
|
||||
mm::{DmaCoherent, FrameAllocOptions},
|
||||
offset_of, Pod,
|
||||
};
|
||||
|
||||
use crate::{dma_buf::DmaBuf, transport::VirtioTransport};
|
||||
use crate::{
|
||||
dma_buf::DmaBuf,
|
||||
transport::{pci::legacy::VirtioPciLegacyTransport, ConfigManager, VirtioTransport},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueueError {
|
||||
@ -40,8 +42,8 @@ pub struct VirtQueue {
|
||||
avail: SafePtr<AvailRing, DmaCoherent>,
|
||||
/// Used ring
|
||||
used: SafePtr<UsedRing, DmaCoherent>,
|
||||
/// point to notify address
|
||||
notify: SafePtr<u32, IoMem>,
|
||||
/// Notify configuration manager
|
||||
notify_config: ConfigManager<u32>,
|
||||
|
||||
/// The index of queue
|
||||
queue_idx: u32,
|
||||
@ -66,7 +68,7 @@ impl VirtQueue {
|
||||
/// Create a new VirtQueue.
|
||||
pub(crate) fn new(
|
||||
idx: u16,
|
||||
size: u16,
|
||||
mut size: u16,
|
||||
transport: &mut dyn VirtioTransport,
|
||||
) -> Result<Self, QueueError> {
|
||||
if !size.is_power_of_two() {
|
||||
@ -74,17 +76,27 @@ impl VirtQueue {
|
||||
}
|
||||
|
||||
let (descriptor_ptr, avail_ring_ptr, used_ring_ptr) = if transport.is_legacy_version() {
|
||||
// FIXME: How about pci legacy?
|
||||
// Currently, we use one Frame to place the descriptors and available rings, one Frame to place used rings
|
||||
// because the virtio-mmio legacy required the address to be continuous. The max queue size is 128.
|
||||
if size > 128 {
|
||||
return Err(QueueError::InvalidArgs);
|
||||
}
|
||||
let desc_size = size_of::<Descriptor>() * size as usize;
|
||||
let queue_size = transport.max_queue_size(idx).unwrap() as usize;
|
||||
let desc_size = size_of::<Descriptor>() * queue_size;
|
||||
size = queue_size as u16;
|
||||
|
||||
let (seg1, seg2) = {
|
||||
let segment = FrameAllocOptions::new(2).alloc_contiguous().unwrap();
|
||||
segment.split(ostd::mm::PAGE_SIZE)
|
||||
let align_size = VirtioPciLegacyTransport::QUEUE_ALIGN_SIZE;
|
||||
let total_frames =
|
||||
VirtioPciLegacyTransport::calc_virtqueue_size_aligned(queue_size) / align_size;
|
||||
let continue_segment = FrameAllocOptions::new(total_frames)
|
||||
.alloc_contiguous()
|
||||
.unwrap();
|
||||
|
||||
let avial_size = size_of::<u16>() * (3 + queue_size);
|
||||
let seg1_frames = (desc_size + avial_size).div_ceil(align_size);
|
||||
|
||||
continue_segment.split(seg1_frames * align_size)
|
||||
};
|
||||
let desc_frame_ptr: SafePtr<Descriptor, DmaCoherent> =
|
||||
SafePtr::new(DmaCoherent::map(seg1, true).unwrap(), 0);
|
||||
@ -141,7 +153,7 @@ impl VirtQueue {
|
||||
}
|
||||
}
|
||||
|
||||
let notify = transport.get_notify_ptr(idx).unwrap();
|
||||
let notify_config = transport.notify_config(idx as usize);
|
||||
field_ptr!(&avail_ring_ptr, AvailRing, flags)
|
||||
.write_once(&AvailFlags::empty())
|
||||
.unwrap();
|
||||
@ -149,7 +161,7 @@ impl VirtQueue {
|
||||
descs,
|
||||
avail: avail_ring_ptr,
|
||||
used: used_ring_ptr,
|
||||
notify,
|
||||
notify_config,
|
||||
queue_size: size,
|
||||
queue_idx: idx as u32,
|
||||
num_used: 0,
|
||||
@ -343,7 +355,15 @@ impl VirtQueue {
|
||||
|
||||
/// notify that there are available rings
|
||||
pub fn notify(&mut self) {
|
||||
self.notify.write_once(&self.queue_idx).unwrap();
|
||||
if self.notify_config.is_modern() {
|
||||
self.notify_config
|
||||
.write_once::<u32>(0, self.queue_idx)
|
||||
.unwrap();
|
||||
} else {
|
||||
self.notify_config
|
||||
.write_once::<u16>(0, self.queue_idx as u16)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables registered callbacks.
|
||||
|
@ -7,9 +7,12 @@ use aster_rights::{ReadOp, WriteOp};
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use log::warn;
|
||||
use ostd::{
|
||||
bus::mmio::{
|
||||
bus::MmioDevice,
|
||||
common_device::{MmioCommonDevice, VirtioMmioVersion},
|
||||
bus::{
|
||||
mmio::{
|
||||
bus::MmioDevice,
|
||||
common_device::{MmioCommonDevice, VirtioMmioVersion},
|
||||
},
|
||||
pci::cfg_space::Bar,
|
||||
},
|
||||
io_mem::IoMem,
|
||||
mm::{DmaCoherent, PAGE_SIZE},
|
||||
@ -21,7 +24,7 @@ use ostd::{
|
||||
use super::{layout::VirtioMmioLayout, multiplex::MultiplexIrq};
|
||||
use crate::{
|
||||
queue::{AvailRing, Descriptor, UsedRing},
|
||||
transport::{DeviceStatus, VirtioTransport, VirtioTransportError},
|
||||
transport::{ConfigManager, DeviceStatus, VirtioTransport, VirtioTransportError},
|
||||
VirtioDeviceType,
|
||||
};
|
||||
|
||||
@ -170,9 +173,11 @@ impl VirtioTransport for VirtioMmioTransport {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_notify_ptr(&self, _idx: u16) -> Result<SafePtr<u32, IoMem>, VirtioTransportError> {
|
||||
fn notify_config(&self, _idx: usize) -> ConfigManager<u32> {
|
||||
let offset = offset_of!(VirtioMmioLayout, queue_notify) as usize;
|
||||
Ok(SafePtr::new(self.common_device.io_mem().clone(), offset))
|
||||
let safe_ptr = Some(SafePtr::new(self.common_device.io_mem().clone(), offset));
|
||||
|
||||
ConfigManager::new(safe_ptr, None)
|
||||
}
|
||||
|
||||
fn num_queues(&self) -> u16 {
|
||||
@ -196,9 +201,13 @@ impl VirtioTransport for VirtioMmioTransport {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn device_config_memory(&self) -> IoMem {
|
||||
fn device_config_mem(&self) -> Option<IoMem> {
|
||||
// offset: 0x100~0x200
|
||||
self.common_device.io_mem().slice(0x100..0x200)
|
||||
Some(self.common_device.io_mem().slice(0x100..0x200))
|
||||
}
|
||||
|
||||
fn device_config_bar(&self) -> Option<(Bar, usize)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn read_device_features(&self) -> u64 {
|
||||
|
@ -4,7 +4,14 @@ use alloc::boxed::Box;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use ostd::{io_mem::IoMem, mm::DmaCoherent, trap::IrqCallbackFunction};
|
||||
use ostd::{
|
||||
arch::device::io_port::{PortRead, PortWrite},
|
||||
bus::pci::cfg_space::Bar,
|
||||
io_mem::IoMem,
|
||||
mm::{DmaCoherent, PodOnce},
|
||||
trap::IrqCallbackFunction,
|
||||
Pod,
|
||||
};
|
||||
|
||||
use self::{mmio::virtio_mmio_init, pci::virtio_pci_init};
|
||||
use crate::{
|
||||
@ -51,7 +58,10 @@ pub trait VirtioTransport: Sync + Send + Debug {
|
||||
}
|
||||
|
||||
/// Get access to the device config memory.
|
||||
fn device_config_memory(&self) -> IoMem;
|
||||
fn device_config_mem(&self) -> Option<IoMem>;
|
||||
|
||||
/// Get access to the device config BAR space.
|
||||
fn device_config_bar(&self) -> Option<(Bar, usize)>;
|
||||
|
||||
// ====================Virtqueue related APIs====================
|
||||
|
||||
@ -71,9 +81,9 @@ pub trait VirtioTransport: Sync + Send + Debug {
|
||||
/// 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)
|
||||
/// Get notify manager 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 notify_config(&self, idx: usize) -> ConfigManager<u32>;
|
||||
|
||||
fn is_legacy_version(&self) -> bool;
|
||||
|
||||
@ -99,6 +109,115 @@ pub trait VirtioTransport: Sync + Send + Debug {
|
||||
) -> Result<(), VirtioTransportError>;
|
||||
}
|
||||
|
||||
/// Manage PCI device/notify configuration space (legacy/modern).
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigManager<T: Pod> {
|
||||
modern_space: Option<SafePtr<T, IoMem>>,
|
||||
legacy_space: Option<(Bar, usize)>,
|
||||
}
|
||||
|
||||
impl<T: Pod> ConfigManager<T> {
|
||||
pub(super) fn new(
|
||||
modern_space: Option<SafePtr<T, IoMem>>,
|
||||
legacy_space: Option<(Bar, usize)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
modern_space,
|
||||
legacy_space,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return if the modern configuration space exists.
|
||||
pub(super) fn is_modern(&self) -> bool {
|
||||
self.modern_space.is_some()
|
||||
}
|
||||
|
||||
fn read_modern<V: PodOnce + PortRead>(&self, offset: usize) -> Result<V, VirtioTransportError> {
|
||||
let Some(safe_ptr) = self.modern_space.as_ref() else {
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
};
|
||||
|
||||
let field_ptr: SafePtr<V, &IoMem> = {
|
||||
let mut ptr = safe_ptr.borrow_vm();
|
||||
ptr.byte_add(offset);
|
||||
ptr.cast()
|
||||
};
|
||||
|
||||
field_ptr
|
||||
.read_once()
|
||||
.map_err(|_| VirtioTransportError::DeviceStatusError)
|
||||
}
|
||||
|
||||
fn read_legacy<V: PodOnce + PortRead>(&self, offset: usize) -> Result<V, VirtioTransportError> {
|
||||
let Some((bar, base)) = self.legacy_space.as_ref() else {
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
};
|
||||
|
||||
bar.read_once(base + offset)
|
||||
.map_err(|_| VirtioTransportError::DeviceStatusError)
|
||||
}
|
||||
|
||||
/// Read a specific configuration.
|
||||
pub(super) fn read_once<V: PodOnce + PortRead>(
|
||||
&self,
|
||||
offset: usize,
|
||||
) -> Result<V, VirtioTransportError> {
|
||||
debug_assert!(offset + size_of::<V>() <= size_of::<T>());
|
||||
if self.is_modern() {
|
||||
self.read_modern(offset)
|
||||
} else {
|
||||
self.read_legacy(offset)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_modern<V: PodOnce + PortWrite>(
|
||||
&self,
|
||||
offset: usize,
|
||||
value: V,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
let Some(safe_ptr) = self.modern_space.as_ref() else {
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
};
|
||||
|
||||
let field_ptr: SafePtr<V, &IoMem> = {
|
||||
let mut ptr = safe_ptr.borrow_vm();
|
||||
ptr.byte_add(offset);
|
||||
ptr.cast()
|
||||
};
|
||||
|
||||
field_ptr
|
||||
.write_once(&value)
|
||||
.map_err(|_| VirtioTransportError::DeviceStatusError)
|
||||
}
|
||||
|
||||
fn write_legacy<V: PodOnce + PortWrite>(
|
||||
&self,
|
||||
offset: usize,
|
||||
value: V,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
let Some((bar, base)) = self.legacy_space.as_ref() else {
|
||||
return Err(VirtioTransportError::InvalidArgs);
|
||||
};
|
||||
|
||||
bar.write_once(base + offset, value)
|
||||
.map_err(|_| VirtioTransportError::DeviceStatusError)
|
||||
}
|
||||
|
||||
/// Write a specific configuration.
|
||||
pub(super) fn write_once<V: PodOnce + PortWrite>(
|
||||
&self,
|
||||
offset: usize,
|
||||
value: V,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
debug_assert!(offset + size_of::<V>() <= size_of::<T>());
|
||||
if self.is_modern() {
|
||||
self.write_modern(offset, value)
|
||||
} else {
|
||||
self.write_legacy(offset, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum VirtioTransportError {
|
||||
DeviceStatusError,
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use alloc::boxed::Box;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
@ -8,7 +8,8 @@ use log::{info, warn};
|
||||
use ostd::{
|
||||
bus::{
|
||||
pci::{
|
||||
bus::PciDevice, capability::CapabilityData, common_device::PciCommonDevice, PciDeviceId,
|
||||
bus::PciDevice, capability::CapabilityData, cfg_space::Bar,
|
||||
common_device::PciCommonDevice, PciDeviceId,
|
||||
},
|
||||
BusProbeError,
|
||||
},
|
||||
@ -23,7 +24,7 @@ use crate::{
|
||||
queue::{AvailRing, Descriptor, UsedRing},
|
||||
transport::{
|
||||
pci::capability::{VirtioPciCapabilityData, VirtioPciCpabilityType},
|
||||
DeviceStatus, VirtioTransport, VirtioTransportError,
|
||||
ConfigManager, DeviceStatus, VirtioTransport, VirtioTransportError,
|
||||
},
|
||||
VirtioDeviceType,
|
||||
};
|
||||
@ -39,14 +40,10 @@ 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 VirtioPciDevice {
|
||||
pub(super) fn new(device_id: PciDeviceId) -> Self {
|
||||
Self { device_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl PciDevice for VirtioPciDevice {
|
||||
@ -55,15 +52,24 @@ impl PciDevice for VirtioPciDevice {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VirtioPciTransport {
|
||||
pub struct VirtioPciModernTransport {
|
||||
device_type: VirtioDeviceType,
|
||||
common_device: PciCommonDevice,
|
||||
common_cfg: SafePtr<VirtioPciCommonCfg, IoMem>,
|
||||
device_cfg: VirtioPciCapabilityData,
|
||||
notify: VirtioPciNotify,
|
||||
msix_manager: VirtioMsixManager,
|
||||
}
|
||||
|
||||
impl Debug for VirtioPciModernTransport {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("PCIVirtioDevice")
|
||||
f.debug_struct("PCIVirtioModernDevice")
|
||||
.field("common_device", &self.common_device)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioTransport for VirtioPciTransport {
|
||||
impl VirtioTransport for VirtioPciModernTransport {
|
||||
fn device_type(&self) -> VirtioDeviceType {
|
||||
self.device_type
|
||||
}
|
||||
@ -108,14 +114,14 @@ impl VirtioTransport for VirtioPciTransport {
|
||||
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(
|
||||
fn notify_config(&self, idx: usize) -> ConfigManager<u32> {
|
||||
debug_assert!(idx < self.num_queues() as usize);
|
||||
let safe_ptr = Some(SafePtr::new(
|
||||
self.notify.io_memory.clone(),
|
||||
(self.notify.offset + self.notify.offset_multiplier * idx as u32) as usize,
|
||||
))
|
||||
));
|
||||
|
||||
ConfigManager::new(safe_ptr, None)
|
||||
}
|
||||
|
||||
fn num_queues(&self) -> u16 {
|
||||
@ -124,12 +130,22 @@ impl VirtioTransport for VirtioPciTransport {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn device_config_memory(&self) -> IoMem {
|
||||
let memory = self.device_cfg.memory_bar().as_ref().unwrap().io_mem();
|
||||
|
||||
fn device_config_mem(&self) -> Option<IoMem> {
|
||||
let offset = self.device_cfg.offset() as usize;
|
||||
let length = self.device_cfg.length() as usize;
|
||||
memory.slice(offset..offset + length)
|
||||
let io_mem = self
|
||||
.device_cfg
|
||||
.memory_bar()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.io_mem()
|
||||
.slice(offset..offset + length);
|
||||
|
||||
Some(io_mem)
|
||||
}
|
||||
|
||||
fn device_config_bar(&self) -> Option<(Bar, usize)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn read_device_features(&self) -> u64 {
|
||||
@ -251,42 +267,22 @@ impl VirtioTransport for VirtioPciTransport {
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioPciTransport {
|
||||
pub(super) fn pci_device(&self) -> &Arc<VirtioPciDevice> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
impl VirtioPciModernTransport {
|
||||
#[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::DeviceNotMatch, 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::DeviceNotMatch, common_device));
|
||||
}
|
||||
}
|
||||
let device_id = common_device.device_id().device_id;
|
||||
if device_id <= 0x1040 {
|
||||
warn!("Unrecognized virtio-pci device id:{:x?}", device_id);
|
||||
return Err((BusProbeError::DeviceNotMatch, common_device));
|
||||
}
|
||||
|
||||
let device_type = match VirtioDeviceType::try_from((device_id - 0x1040) as u8) {
|
||||
Ok(device) => device,
|
||||
Err(_) => {
|
||||
warn!("Unrecognized virtio-pci device id:{:x?}", device_id);
|
||||
return Err((BusProbeError::DeviceNotMatch, common_device));
|
||||
}
|
||||
};
|
||||
|
||||
@ -335,7 +331,6 @@ impl VirtioPciTransport {
|
||||
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,
|
||||
@ -343,7 +338,6 @@ impl VirtioPciTransport {
|
||||
notify,
|
||||
msix_manager,
|
||||
device_type,
|
||||
device: Arc::new(VirtioPciDevice { device_id }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
|
||||
use ostd::{
|
||||
bus::{
|
||||
@ -13,11 +13,15 @@ use ostd::{
|
||||
sync::SpinLock,
|
||||
};
|
||||
|
||||
use super::device::VirtioPciTransport;
|
||||
use super::device::VirtioPciModernTransport;
|
||||
use crate::transport::{
|
||||
pci::{device::VirtioPciDevice, legacy::VirtioPciLegacyTransport},
|
||||
VirtioTransport,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VirtioPciDriver {
|
||||
devices: SpinLock<Vec<VirtioPciTransport>>,
|
||||
devices: SpinLock<Vec<Box<dyn VirtioTransport>>>,
|
||||
}
|
||||
|
||||
impl VirtioPciDriver {
|
||||
@ -25,7 +29,7 @@ impl VirtioPciDriver {
|
||||
self.devices.lock().len()
|
||||
}
|
||||
|
||||
pub fn pop_device_transport(&self) -> Option<VirtioPciTransport> {
|
||||
pub fn pop_device_transport(&self) -> Option<Box<dyn VirtioTransport>> {
|
||||
self.devices.lock().pop()
|
||||
}
|
||||
|
||||
@ -45,9 +49,22 @@ impl PciDriver for VirtioPciDriver {
|
||||
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();
|
||||
|
||||
let device_id = *device.device_id();
|
||||
let transport: Box<dyn VirtioTransport> = match device_id.device_id {
|
||||
0x1000..0x1040 if (device.device_id().revision_id == 0) => {
|
||||
// Transitional PCI Device ID in the range 0x1000 to 0x103f.
|
||||
let legacy = VirtioPciLegacyTransport::new(device)?;
|
||||
Box::new(legacy)
|
||||
}
|
||||
0x1040..0x107f => {
|
||||
let modern = VirtioPciModernTransport::new(device)?;
|
||||
Box::new(modern)
|
||||
}
|
||||
_ => return Err((BusProbeError::DeviceNotMatch, device)),
|
||||
};
|
||||
self.devices.lock().push(transport);
|
||||
Ok(device)
|
||||
|
||||
Ok(Arc::new(VirtioPciDevice::new(device_id)))
|
||||
}
|
||||
}
|
||||
|
335
kernel/comps/virtio/src/transport/pci/legacy.rs
Normal file
335
kernel/comps/virtio/src/transport/pci/legacy.rs
Normal file
@ -0,0 +1,335 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use log::{info, warn};
|
||||
use ostd::{
|
||||
bus::{
|
||||
pci::{capability::CapabilityData, cfg_space::Bar, common_device::PciCommonDevice},
|
||||
BusProbeError,
|
||||
},
|
||||
io_mem::IoMem,
|
||||
mm::{DmaCoherent, HasDaddr, PAGE_SIZE},
|
||||
trap::IrqCallbackFunction,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
queue::UsedElem,
|
||||
transport::{
|
||||
pci::msix::VirtioMsixManager, AvailRing, ConfigManager, Descriptor, UsedRing,
|
||||
VirtioTransport, VirtioTransportError,
|
||||
},
|
||||
DeviceStatus, VirtioDeviceType,
|
||||
};
|
||||
|
||||
// When used through the legacy interface, the virtio common configuration structure looks as follows:
|
||||
//
|
||||
// virtio legacy ==> Mapped into PCI BAR0
|
||||
// +---------------------------------------------------------------------------------------+
|
||||
// | Device Features Bits[0:31] (Read-only) |
|
||||
// +---------------------------------------------------------------------------------------+
|
||||
// | Driver Features Bits[0:31] (Read & Write) |
|
||||
// +---------------------------------------------------------------------------------------+
|
||||
// | Virtqueue Address PFN (R/W) |
|
||||
// +-------------------------------------------+-------------------------------------------+
|
||||
// | Queue Select (R/W) | Queue Size (R) |
|
||||
// +---------------------+---------------------+-------------------------------------------+
|
||||
// | ISR Status (R) | Device Status (R/W) | Queue Notify (R/W) |
|
||||
// +---------------------+---------------------+-------------------------------------------+
|
||||
// | MSIX Queue Vector (R/W) | MSIX Config Vector (R/W) |
|
||||
// +-------------------------------------------+-------------------------------------------+
|
||||
// | Device Specific Configurations (R/W) |
|
||||
// +---------------------------------------------------------------------------------------+
|
||||
//
|
||||
// When MSI-X capability is enabled, device-specific configuration starts at byte offset 24 in
|
||||
// virtio common configuration structure. When MSI-X capability is not enabled, device-specific
|
||||
// configuration starts at byte offset 20 in virtio header.
|
||||
const DEVICE_FEATURES_OFFSET: usize = 0x00;
|
||||
const DRIVER_FEATURES_OFFSET: usize = 0x04;
|
||||
const QUEUE_ADDR_PFN_OFFSET: usize = 0x08;
|
||||
const QUEUE_SIZE_OFFSET: usize = 0x0c;
|
||||
const QUEUE_SELECT_OFFSET: usize = 0x0e;
|
||||
const QUEUE_NOTIFY_OFFSET: usize = 0x10;
|
||||
const DEVICE_STATUS_OFFSET: usize = 0x12;
|
||||
const ISR_STATUS_OFFSET: usize = 0x13;
|
||||
// If MSI-X is enabled for the device, there are two additional fields.
|
||||
const CONFIG_MSIX_VECTOR_OFFSET: usize = 0x14;
|
||||
const QUEUE_MSIX_VECTOR_OFFSET: usize = 0x16;
|
||||
|
||||
const DEVICE_CONFIG_OFFSET: usize = 0x14;
|
||||
const DEVICE_CONFIG_OFFSET_WITH_MSIX: usize = 0x18;
|
||||
|
||||
pub struct VirtioPciLegacyTransport {
|
||||
device_type: VirtioDeviceType,
|
||||
common_device: PciCommonDevice,
|
||||
config_bar: Bar,
|
||||
num_queues: u16,
|
||||
msix_manager: VirtioMsixManager,
|
||||
}
|
||||
|
||||
impl VirtioPciLegacyTransport {
|
||||
pub const QUEUE_ALIGN_SIZE: usize = 4096;
|
||||
|
||||
#[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,
|
||||
_ => {
|
||||
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 config_bar = common_device.bar_manager().bar(0).unwrap();
|
||||
|
||||
let mut num_queues = 0u16;
|
||||
while num_queues < u16::MAX {
|
||||
config_bar
|
||||
.write_once(QUEUE_SELECT_OFFSET, num_queues)
|
||||
.unwrap();
|
||||
let queue_size = config_bar.read_once::<u16>(QUEUE_SIZE_OFFSET).unwrap();
|
||||
if queue_size == 0 {
|
||||
break;
|
||||
}
|
||||
num_queues += 1;
|
||||
}
|
||||
|
||||
// TODO: Support interrupt without MSI-X
|
||||
let mut msix = None;
|
||||
for cap in common_device.capabilities().iter() {
|
||||
match cap.capability_data() {
|
||||
CapabilityData::Msix(data) => {
|
||||
msix = Some(data.clone());
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
let Some(msix) = msix else {
|
||||
return Err((BusProbeError::ConfigurationSpaceError, common_device));
|
||||
};
|
||||
let msix_manager = VirtioMsixManager::new(msix);
|
||||
|
||||
Ok(Self {
|
||||
device_type,
|
||||
common_device,
|
||||
config_bar,
|
||||
num_queues,
|
||||
msix_manager,
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate the aligned virtqueue size.
|
||||
///
|
||||
/// According to the VirtIO spec v0.9.5:
|
||||
///
|
||||
/// Each virtqueue occupies two or more physically-contiguous pages (defined, for
|
||||
/// the purposes of this specification, as 4096 bytes), and consists of three parts:
|
||||
/// +------------------+------------------------------------------------+-----------+
|
||||
/// | Descriptor Table | Available Ring (padding to next 4096 boundary) | Used Ring |
|
||||
/// +------------------+------------------------------------------------+-----------+
|
||||
///
|
||||
/// More details can be found at <http://ozlabs.org/~rusty/virtio-spec/virtio-0.9.5.pdf>.
|
||||
pub(crate) fn calc_virtqueue_size_aligned(queue_size: usize) -> usize {
|
||||
let align_mask = Self::QUEUE_ALIGN_SIZE - 1;
|
||||
|
||||
((size_of::<Descriptor>() * queue_size + size_of::<u16>() * (3 + queue_size) + align_mask)
|
||||
& !align_mask)
|
||||
+ ((size_of::<u16>() * 3 + size_of::<UsedElem>() * queue_size + align_mask)
|
||||
& !align_mask)
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioTransport for VirtioPciLegacyTransport {
|
||||
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> {
|
||||
// When using the legacy interface, there was no mechanism to negotiate
|
||||
// the queue size! The transitional driver MUST retrieve the `Queue Size`
|
||||
// field from the device and MUST allocate the total number of bytes
|
||||
// (including descriptor, avail_ring and used_ring) for the virtqueue
|
||||
// according to the specific formula, see `calc_virtqueue_size_aligned`.
|
||||
let queue_addr = descriptor_ptr.daddr();
|
||||
let page_frame_number = (queue_addr / PAGE_SIZE) as u32;
|
||||
|
||||
self.config_bar
|
||||
.write_once(QUEUE_SELECT_OFFSET, idx)
|
||||
.unwrap();
|
||||
debug_assert_eq!(
|
||||
self.config_bar
|
||||
.read_once::<u16>(QUEUE_SELECT_OFFSET)
|
||||
.unwrap(),
|
||||
idx
|
||||
);
|
||||
self.config_bar
|
||||
.write_once(QUEUE_ADDR_PFN_OFFSET, page_frame_number)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_config(&self, _idx: usize) -> ConfigManager<u32> {
|
||||
let bar_space = Some((self.config_bar.clone(), QUEUE_NOTIFY_OFFSET));
|
||||
|
||||
ConfigManager::new(None, bar_space)
|
||||
}
|
||||
|
||||
fn num_queues(&self) -> u16 {
|
||||
self.num_queues
|
||||
}
|
||||
|
||||
fn device_config_mem(&self) -> Option<IoMem> {
|
||||
None
|
||||
}
|
||||
|
||||
fn device_config_bar(&self) -> Option<(Bar, usize)> {
|
||||
let bar = self.config_bar.clone();
|
||||
let base = if self.msix_manager.is_enabled() {
|
||||
DEVICE_CONFIG_OFFSET_WITH_MSIX
|
||||
} else {
|
||||
DEVICE_CONFIG_OFFSET
|
||||
};
|
||||
|
||||
Some((bar, base))
|
||||
}
|
||||
|
||||
fn read_device_features(&self) -> u64 {
|
||||
// Only Feature Bits 0 to 31 are accessible through the Legacy Interface.
|
||||
let features = self
|
||||
.config_bar
|
||||
.read_once::<u32>(DEVICE_FEATURES_OFFSET)
|
||||
.unwrap();
|
||||
features as u64
|
||||
}
|
||||
|
||||
fn write_driver_features(&mut self, features: u64) -> Result<(), VirtioTransportError> {
|
||||
// When used through the Legacy Interface, Transitional Devices MUST assume that
|
||||
// Feature Bits 32 to 63 are not acknowledged by Driver.
|
||||
self.config_bar
|
||||
.write_once(DRIVER_FEATURES_OFFSET, features as u32)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_device_status(&self) -> DeviceStatus {
|
||||
let status = self
|
||||
.config_bar
|
||||
.read_once::<u8>(DEVICE_STATUS_OFFSET)
|
||||
.unwrap();
|
||||
DeviceStatus::from_bits_truncate(status)
|
||||
}
|
||||
|
||||
fn write_device_status(&mut self, status: DeviceStatus) -> Result<(), VirtioTransportError> {
|
||||
let status = status.bits();
|
||||
self.config_bar
|
||||
.write_once(DEVICE_STATUS_OFFSET, status)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Set to driver ok status
|
||||
fn finish_init(&mut self) {
|
||||
self.write_device_status(
|
||||
DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::DRIVER_OK,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn max_queue_size(&self, idx: u16) -> Result<u16, VirtioTransportError> {
|
||||
self.config_bar
|
||||
.write_once(QUEUE_SELECT_OFFSET, idx)
|
||||
.unwrap();
|
||||
debug_assert_eq!(
|
||||
self.config_bar
|
||||
.read_once::<u16>(QUEUE_SELECT_OFFSET)
|
||||
.unwrap(),
|
||||
idx
|
||||
);
|
||||
Ok(self.config_bar.read_once(QUEUE_SIZE_OFFSET).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 {
|
||||
if let Some(unused_irq) = self.msix_manager.pop_unused_irq() {
|
||||
unused_irq
|
||||
} else {
|
||||
warn!(
|
||||
"{:?}: `single_interrupt` ignored: no more IRQ lines available",
|
||||
self.device_type()
|
||||
);
|
||||
self.msix_manager.shared_irq_line()
|
||||
}
|
||||
} else {
|
||||
self.msix_manager.shared_irq_line()
|
||||
};
|
||||
irq.on_active(func);
|
||||
|
||||
self.config_bar
|
||||
.write_once(QUEUE_SELECT_OFFSET, index)
|
||||
.unwrap();
|
||||
debug_assert_eq!(
|
||||
self.config_bar
|
||||
.read_once::<u16>(QUEUE_SELECT_OFFSET)
|
||||
.unwrap(),
|
||||
index
|
||||
);
|
||||
self.config_bar
|
||||
.write_once(QUEUE_MSIX_VECTOR_OFFSET, vector)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_cfg_callback(
|
||||
&mut self,
|
||||
func: Box<IrqCallbackFunction>,
|
||||
) -> Result<(), VirtioTransportError> {
|
||||
let (vector, irq) = self.msix_manager.config_msix_irq();
|
||||
irq.on_active(func);
|
||||
|
||||
self.config_bar
|
||||
.write_once(CONFIG_MSIX_VECTOR_OFFSET, vector)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_legacy_version(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VirtioPciLegacyTransport {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("PCIVirtioLegacyDevice")
|
||||
.field("common_device", &self.common_device)
|
||||
.finish()
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ pub mod capability;
|
||||
pub mod common_cfg;
|
||||
pub mod device;
|
||||
pub mod driver;
|
||||
pub mod legacy;
|
||||
pub(super) mod msix;
|
||||
|
||||
use alloc::sync::Arc;
|
||||
|
@ -62,4 +62,9 @@ impl VirtioMsixManager {
|
||||
self.used_msix_vectors.push(vector);
|
||||
Some((vector, self.msix.irq_mut(vector as usize).unwrap()))
|
||||
}
|
||||
|
||||
/// Returns true if MSI-X is enabled.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.msix.is_enabled()
|
||||
}
|
||||
}
|
||||
|
@ -217,6 +217,12 @@ impl CapabilityMsixData {
|
||||
pub fn irq_mut(&mut self, index: usize) -> Option<&mut IrqLine> {
|
||||
self.irqs[index].as_mut()
|
||||
}
|
||||
|
||||
/// Returns true if MSI-X Enable bit is set.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
let msg_ctrl = self.loc.read16(self.ptr + 2);
|
||||
msg_ctrl & 0x8000 != 0
|
||||
}
|
||||
}
|
||||
|
||||
fn set_bit(origin_value: u16, offset: usize, set: bool) -> u16 {
|
||||
|
@ -13,7 +13,10 @@ use super::PciDeviceLocation;
|
||||
use crate::{
|
||||
arch::device::io_port::{PortRead, PortWrite},
|
||||
io_mem::IoMem,
|
||||
mm::page_prop::{CachePolicy, PageFlags},
|
||||
mm::{
|
||||
page_prop::{CachePolicy, PageFlags},
|
||||
PodOnce, VmIoOnce,
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
@ -175,6 +178,22 @@ impl Bar {
|
||||
Self::Io(Arc::new(IoBar::new(&location, index)?))
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads a value of a specified type at a specified offset.
|
||||
pub fn read_once<T: PodOnce + PortRead>(&self, offset: usize) -> Result<T> {
|
||||
match self {
|
||||
Bar::Memory(mem_bar) => mem_bar.io_mem().read_once(offset),
|
||||
Bar::Io(io_bar) => io_bar.read(offset as u32),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a value of a specified type at a specified offset.
|
||||
pub fn write_once<T: PodOnce + PortWrite>(&self, offset: usize, value: T) -> Result<()> {
|
||||
match self {
|
||||
Bar::Memory(mem_bar) => mem_bar.io_mem().write_once(offset, &value),
|
||||
Bar::Io(io_bar) => io_bar.write(offset as u32, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory BAR
|
||||
|
Reference in New Issue
Block a user