mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-20 13:06:33 +00:00
Refactor project structure
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
bd878dd1c9
commit
e3c227ae06
300
kernel/comps/virtio/src/device/block/device.rs
Normal file
300
kernel/comps/virtio/src/device/block/device.rs
Normal file
@ -0,0 +1,300 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
|
||||
use core::{fmt::Debug, hint::spin_loop, mem::size_of};
|
||||
|
||||
use aster_block::{
|
||||
bio::{BioEnqueueError, BioStatus, BioType, SubmittedBio},
|
||||
id::Sid,
|
||||
request_queue::{BioRequest, BioRequestSingleQueue},
|
||||
};
|
||||
use aster_frame::{
|
||||
io_mem::IoMem,
|
||||
sync::SpinLock,
|
||||
trap::TrapFrame,
|
||||
vm::{VmAllocOptions, VmFrame, VmIo, VmReader, VmWriter},
|
||||
};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use log::info;
|
||||
use pod::Pod;
|
||||
|
||||
use super::{BlockFeatures, VirtioBlockConfig};
|
||||
use crate::{
|
||||
device::{
|
||||
block::{ReqType, RespStatus},
|
||||
VirtioDeviceError,
|
||||
},
|
||||
queue::VirtQueue,
|
||||
transport::VirtioTransport,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BlockDevice {
|
||||
device: DeviceInner,
|
||||
/// The software staging queue.
|
||||
queue: BioRequestSingleQueue,
|
||||
}
|
||||
|
||||
impl BlockDevice {
|
||||
/// Creates a new VirtIO-Block driver and registers it.
|
||||
pub(crate) fn init(transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let block_device = {
|
||||
let device = DeviceInner::init(transport)?;
|
||||
Self {
|
||||
device,
|
||||
queue: BioRequestSingleQueue::new(),
|
||||
}
|
||||
};
|
||||
aster_block::register_device(super::DEVICE_NAME.to_string(), Arc::new(block_device));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dequeues a `BioRequest` from the software staging queue and
|
||||
/// processes the request.
|
||||
///
|
||||
/// TODO: Current read and write operations are still synchronous,
|
||||
/// it needs to be modified to use the queue-based asynchronous programming pattern.
|
||||
pub fn handle_requests(&self) {
|
||||
let request = self.queue.dequeue();
|
||||
match request.type_() {
|
||||
BioType::Read => self.do_read(&request),
|
||||
BioType::Write => self.do_write(&request),
|
||||
BioType::Flush | BioType::Discard => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_read(&self, request: &BioRequest) {
|
||||
let start_sid = request.sid_range().start;
|
||||
|
||||
let writers = {
|
||||
let mut writers = Vec::new();
|
||||
for bio in request.bios() {
|
||||
for segment in bio.segments() {
|
||||
writers.push(segment.writer());
|
||||
}
|
||||
}
|
||||
writers
|
||||
};
|
||||
|
||||
self.device.read(start_sid, writers.as_slice());
|
||||
|
||||
for bio in request.bios() {
|
||||
bio.complete(BioStatus::Complete);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_write(&self, request: &BioRequest) {
|
||||
let start_sid = request.sid_range().start;
|
||||
|
||||
let readers = {
|
||||
let mut readers = Vec::new();
|
||||
for bio in request.bios() {
|
||||
for segment in bio.segments() {
|
||||
readers.push(segment.reader());
|
||||
}
|
||||
}
|
||||
readers
|
||||
};
|
||||
|
||||
self.device.write(start_sid, readers.as_slice());
|
||||
|
||||
for bio in request.bios() {
|
||||
bio.complete(BioStatus::Complete);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
impl aster_block::BlockDevice for BlockDevice {
|
||||
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> {
|
||||
self.queue.enqueue(bio)
|
||||
}
|
||||
|
||||
fn handle_irq(&self) {
|
||||
info!("Virtio block device handle irq");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DeviceInner {
|
||||
config: SafePtr<VirtioBlockConfig, IoMem>,
|
||||
queue: SpinLock<VirtQueue>,
|
||||
transport: Box<dyn VirtioTransport>,
|
||||
/// Block requests, we use VmFrame to store the requests so that
|
||||
/// it can pass to the `add_vm` function
|
||||
block_requests: VmFrame,
|
||||
/// Block responses, we use VmFrame to store the requests so that
|
||||
/// it can pass to the `add_vm` function
|
||||
block_responses: VmFrame,
|
||||
id_allocator: SpinLock<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl DeviceInner {
|
||||
/// Creates and inits the device.
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<Self, VirtioDeviceError> {
|
||||
let config = VirtioBlockConfig::new(transport.as_mut());
|
||||
let num_queues = transport.num_queues();
|
||||
if num_queues != 1 {
|
||||
return Err(VirtioDeviceError::QueuesAmountDoNotMatch(num_queues, 1));
|
||||
}
|
||||
let queue = VirtQueue::new(0, 64, transport.as_mut()).expect("create virtqueue failed");
|
||||
let mut device = Self {
|
||||
config,
|
||||
queue: SpinLock::new(queue),
|
||||
transport,
|
||||
block_requests: VmAllocOptions::new(1).alloc_single().unwrap(),
|
||||
block_responses: VmAllocOptions::new(1).alloc_single().unwrap(),
|
||||
id_allocator: SpinLock::new((0..64).collect()),
|
||||
};
|
||||
|
||||
device
|
||||
.transport
|
||||
.register_cfg_callback(Box::new(config_space_change))
|
||||
.unwrap();
|
||||
device
|
||||
.transport
|
||||
.register_queue_callback(0, Box::new(handle_block_device), false)
|
||||
.unwrap();
|
||||
|
||||
fn handle_block_device(_: &TrapFrame) {
|
||||
aster_block::get_device(super::DEVICE_NAME)
|
||||
.unwrap()
|
||||
.handle_irq();
|
||||
}
|
||||
|
||||
fn config_space_change(_: &TrapFrame) {
|
||||
info!("Virtio block device config space change");
|
||||
}
|
||||
device.transport.finish_init();
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
/// Reads data from the block device, this function is blocking.
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn read(&self, sector_id: Sid, buf: &[VmWriter]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlockReq {
|
||||
type_: ReqType::In as _,
|
||||
reserved: 0,
|
||||
sector: sector_id.to_raw(),
|
||||
};
|
||||
let resp = BlockResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlockReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlockResp>(), &resp)
|
||||
.unwrap();
|
||||
let req_reader = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlockReq>())
|
||||
.limit(size_of::<BlockReq>());
|
||||
let resp_writer = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlockResp>())
|
||||
.limit(size_of::<BlockResp>());
|
||||
|
||||
let mut outputs: Vec<&VmWriter<'_>> = buf.iter().collect();
|
||||
outputs.push(&resp_writer);
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let token = queue
|
||||
.add_vm(&[&req_reader], outputs.as_slice())
|
||||
.expect("add queue failed");
|
||||
queue.notify();
|
||||
while !queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
queue.pop_used_with_token(token).expect("pop used failed");
|
||||
let resp: BlockResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlockResp>())
|
||||
.unwrap();
|
||||
self.id_allocator.lock().push(id as u8);
|
||||
match RespStatus::try_from(resp.status).unwrap() {
|
||||
RespStatus::Ok => {}
|
||||
_ => panic!("io error in block device"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Writes data to the block device, this function is blocking.
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn write(&self, sector_id: Sid, buf: &[VmReader]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlockReq {
|
||||
type_: ReqType::Out as _,
|
||||
reserved: 0,
|
||||
sector: sector_id.to_raw(),
|
||||
};
|
||||
let resp = BlockResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlockReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlockResp>(), &resp)
|
||||
.unwrap();
|
||||
let req_reader = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlockReq>())
|
||||
.limit(size_of::<BlockReq>());
|
||||
let resp_writer = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlockResp>())
|
||||
.limit(size_of::<BlockResp>());
|
||||
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let mut inputs: Vec<&VmReader<'_>> = buf.iter().collect();
|
||||
inputs.insert(0, &req_reader);
|
||||
let token = queue
|
||||
.add_vm(inputs.as_slice(), &[&resp_writer])
|
||||
.expect("add queue failed");
|
||||
queue.notify();
|
||||
while !queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
queue.pop_used_with_token(token).expect("pop used failed");
|
||||
let resp: BlockResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlockResp>())
|
||||
.unwrap();
|
||||
self.id_allocator.lock().push(id as u8);
|
||||
match RespStatus::try_from(resp.status).unwrap() {
|
||||
RespStatus::Ok => {}
|
||||
_ => panic!("io error in block device:{:?}", resp.status),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
struct BlockReq {
|
||||
pub type_: u32,
|
||||
pub reserved: u32,
|
||||
pub sector: u64,
|
||||
}
|
||||
|
||||
/// Response of a VirtIOBlock request.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
struct BlockResp {
|
||||
pub status: u8,
|
||||
}
|
||||
|
||||
impl Default for BlockResp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
status: RespStatus::_NotReady as _,
|
||||
}
|
||||
}
|
||||
}
|
97
kernel/comps/virtio/src/device/block/mod.rs
Normal file
97
kernel/comps/virtio/src/device/block/mod.rs
Normal file
@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod device;
|
||||
|
||||
use aster_frame::io_mem::IoMem;
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use bitflags::bitflags;
|
||||
use int_to_c_enum::TryFromInt;
|
||||
use pod::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
pub static DEVICE_NAME: &str = "Virtio-Block";
|
||||
|
||||
bitflags! {
|
||||
/// features for virtio block device
|
||||
pub(crate) struct BlockFeatures : u64 {
|
||||
const BARRIER = 1 << 0;
|
||||
const SIZE_MAX = 1 << 1;
|
||||
const SEG_MAX = 1 << 2;
|
||||
const GEOMETRY = 1 << 4;
|
||||
const RO = 1 << 5;
|
||||
const BLK_SIZE = 1 << 6;
|
||||
const SCSI = 1 << 7;
|
||||
const FLUSH = 1 << 9;
|
||||
const TOPOLOGY = 1 << 10;
|
||||
const CONFIG_WCE = 1 << 11;
|
||||
const DISCARD = 1 << 13;
|
||||
const WRITE_ZEROES = 1 << 14;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone, TryFromInt)]
|
||||
pub enum ReqType {
|
||||
In = 0,
|
||||
Out = 1,
|
||||
Flush = 4,
|
||||
Discard = 11,
|
||||
WriteZeroes = 13,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, TryFromInt)]
|
||||
pub enum RespStatus {
|
||||
/// Ok.
|
||||
Ok = 0,
|
||||
/// IoErr.
|
||||
IoErr = 1,
|
||||
/// Unsupported yet.
|
||||
Unsupported = 2,
|
||||
/// Not ready.
|
||||
_NotReady = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlockConfig {
|
||||
capacity: u64,
|
||||
size_max: u64,
|
||||
geometry: VirtioBlockGeometry,
|
||||
blk_size: u32,
|
||||
topology: VirtioBlockTopology,
|
||||
writeback: u8,
|
||||
unused0: [u8; 3],
|
||||
max_discard_sectors: u32,
|
||||
max_discard_seg: u32,
|
||||
discard_sector_alignment: u32,
|
||||
max_write_zeroes_sectors: u32,
|
||||
max_write_zeroes_seg: u32,
|
||||
write_zeros_may_unmap: u8,
|
||||
unused1: [u8; 3],
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlockGeometry {
|
||||
cylinders: u16,
|
||||
heads: u8,
|
||||
sectors: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlockTopology {
|
||||
physical_block_exp: u8,
|
||||
alignment_offset: u8,
|
||||
min_io_size: u16,
|
||||
opt_io_size: u32,
|
||||
}
|
||||
|
||||
impl VirtioBlockConfig {
|
||||
pub(self) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
}
|
||||
}
|
36
kernel/comps/virtio/src/device/console/config.rs
Normal file
36
kernel/comps/virtio/src/device/console/config.rs
Normal file
@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_frame::io_mem::IoMem;
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use pod::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
bitflags::bitflags! {
|
||||
pub struct ConsoleFeatures: u64{
|
||||
/// Configuration cols and rows are valid.
|
||||
const VIRTIO_CONSOLE_F_SIZE = 1 << 0;
|
||||
/// Device has support for multiple ports;
|
||||
/// max_nr_ports is valid and control virtqueues will be used.
|
||||
const VIRTIO_CONSOLE_F_MULTIPORT = 1 << 1;
|
||||
/// Device has support for emergency write.
|
||||
/// Configuration field emerg_wr is valid.
|
||||
const VIRTIO_CONSOLE_F_EMERG_WRITE = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Pod, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioConsoleConfig {
|
||||
pub cols: u16,
|
||||
pub row: 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)
|
||||
}
|
||||
}
|
146
kernel/comps/virtio/src/device/console/device.rs
Normal file
146
kernel/comps/virtio/src/device/console/device.rs
Normal file
@ -0,0 +1,146 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, fmt::Debug, string::ToString, sync::Arc, vec::Vec};
|
||||
use core::hint::spin_loop;
|
||||
|
||||
use aster_console::{AnyConsoleDevice, ConsoleCallback};
|
||||
use aster_frame::{config::PAGE_SIZE, io_mem::IoMem, sync::SpinLock, trap::TrapFrame};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use log::debug;
|
||||
|
||||
use super::{config::VirtioConsoleConfig, DEVICE_NAME};
|
||||
use crate::{
|
||||
device::{console::config::ConsoleFeatures, VirtioDeviceError},
|
||||
queue::VirtQueue,
|
||||
transport::VirtioTransport,
|
||||
};
|
||||
|
||||
pub struct ConsoleDevice {
|
||||
config: SafePtr<VirtioConsoleConfig, IoMem>,
|
||||
transport: Box<dyn VirtioTransport>,
|
||||
receive_queue: SpinLock<VirtQueue>,
|
||||
transmit_queue: SpinLock<VirtQueue>,
|
||||
buffer: SpinLock<Box<[u8; PAGE_SIZE]>>,
|
||||
callbacks: SpinLock<Vec<&'static ConsoleCallback>>,
|
||||
}
|
||||
|
||||
impl AnyConsoleDevice for ConsoleDevice {
|
||||
fn send(&self, value: &[u8]) {
|
||||
let mut transmit_queue = self.transmit_queue.lock_irq_disabled();
|
||||
transmit_queue.add_buf(&[value], &[]).unwrap();
|
||||
if transmit_queue.should_notify() {
|
||||
transmit_queue.notify();
|
||||
}
|
||||
while !transmit_queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
transmit_queue.pop_used().unwrap();
|
||||
}
|
||||
|
||||
fn recv(&self, buf: &mut [u8]) -> Option<usize> {
|
||||
let mut receive_queue = self.receive_queue.lock_irq_disabled();
|
||||
if !receive_queue.can_pop() {
|
||||
return None;
|
||||
}
|
||||
let (_, len) = receive_queue.pop_used().unwrap();
|
||||
|
||||
let mut recv_buffer = self.buffer.lock();
|
||||
buf.copy_from_slice(&recv_buffer.as_ref()[..len as usize]);
|
||||
receive_queue.add_buf(&[], &[recv_buffer.as_mut()]).unwrap();
|
||||
if receive_queue.should_notify() {
|
||||
receive_queue.notify();
|
||||
}
|
||||
Some(len as usize)
|
||||
}
|
||||
|
||||
fn register_callback(&self, callback: &'static (dyn Fn(&[u8]) + Send + Sync)) {
|
||||
self.callbacks.lock().push(callback);
|
||||
}
|
||||
|
||||
fn handle_irq(&self) {
|
||||
let mut receive_queue = self.receive_queue.lock_irq_disabled();
|
||||
if !receive_queue.can_pop() {
|
||||
return;
|
||||
}
|
||||
let (_, len) = receive_queue.pop_used().unwrap();
|
||||
let mut recv_buffer = self.buffer.lock();
|
||||
let buffer = &recv_buffer.as_ref()[..len as usize];
|
||||
let lock = self.callbacks.lock();
|
||||
for callback in lock.iter() {
|
||||
callback.call((buffer,));
|
||||
}
|
||||
receive_queue.add_buf(&[], &[recv_buffer.as_mut()]).unwrap();
|
||||
if receive_queue.should_notify() {
|
||||
receive_queue.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ConsoleDevice {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("ConsoleDevice")
|
||||
.field("config", &self.config)
|
||||
.field("transport", &self.transport)
|
||||
.field("receive_queue", &self.receive_queue)
|
||||
.field("transmit_queue", &self.transmit_queue)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConsoleDevice {
|
||||
pub fn negotiate_features(features: u64) -> u64 {
|
||||
let mut features = ConsoleFeatures::from_bits_truncate(features);
|
||||
// A virtio console device may have multiple ports, but we only use one port to communicate now.
|
||||
features.remove(ConsoleFeatures::VIRTIO_CONSOLE_F_MULTIPORT);
|
||||
features.bits()
|
||||
}
|
||||
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let config = VirtioConsoleConfig::new(transport.as_ref());
|
||||
const RECV0_QUEUE_INDEX: u16 = 0;
|
||||
const TRANSMIT0_QUEUE_INDEX: u16 = 1;
|
||||
let receive_queue =
|
||||
SpinLock::new(VirtQueue::new(RECV0_QUEUE_INDEX, 2, transport.as_mut()).unwrap());
|
||||
let transmit_queue =
|
||||
SpinLock::new(VirtQueue::new(TRANSMIT0_QUEUE_INDEX, 2, transport.as_mut()).unwrap());
|
||||
|
||||
let mut device = Self {
|
||||
config,
|
||||
transport,
|
||||
receive_queue,
|
||||
transmit_queue,
|
||||
buffer: SpinLock::new(Box::new([0; PAGE_SIZE])),
|
||||
callbacks: SpinLock::new(Vec::new()),
|
||||
};
|
||||
|
||||
let mut receive_queue = device.receive_queue.lock();
|
||||
receive_queue
|
||||
.add_buf(&[], &[device.buffer.lock().as_mut()])
|
||||
.unwrap();
|
||||
if receive_queue.should_notify() {
|
||||
receive_queue.notify();
|
||||
}
|
||||
drop(receive_queue);
|
||||
device
|
||||
.transport
|
||||
.register_queue_callback(RECV0_QUEUE_INDEX, Box::new(handle_console_input), false)
|
||||
.unwrap();
|
||||
device
|
||||
.transport
|
||||
.register_cfg_callback(Box::new(config_space_change))
|
||||
.unwrap();
|
||||
device.transport.finish_init();
|
||||
|
||||
aster_console::register_device(DEVICE_NAME.to_string(), Arc::new(device));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_console_input(_: &TrapFrame) {
|
||||
aster_console::get_device(DEVICE_NAME).unwrap().handle_irq();
|
||||
}
|
||||
|
||||
fn config_space_change(_: &TrapFrame) {
|
||||
debug!("Virtio-Console device configuration space change");
|
||||
}
|
6
kernel/comps/virtio/src/device/console/mod.rs
Normal file
6
kernel/comps/virtio/src/device/console/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
|
||||
pub static DEVICE_NAME: &str = "Virtio-Console";
|
235
kernel/comps/virtio/src/device/input/device.rs
Normal file
235
kernel/comps/virtio/src/device/input/device.rs
Normal file
@ -0,0 +1,235 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
vec::Vec,
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
|
||||
use aster_frame::{io_mem::IoMem, offset_of, sync::SpinLock, trap::TrapFrame};
|
||||
use aster_input::{
|
||||
key::{Key, KeyStatus},
|
||||
InputEvent,
|
||||
};
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use bitflags::bitflags;
|
||||
use log::{debug, info};
|
||||
use pod::Pod;
|
||||
|
||||
use super::{InputConfigSelect, VirtioInputConfig, VirtioInputEvent, QUEUE_EVENT, QUEUE_STATUS};
|
||||
use crate::{device::VirtioDeviceError, queue::VirtQueue, transport::VirtioTransport};
|
||||
|
||||
bitflags! {
|
||||
/// The properties of input device.
|
||||
///
|
||||
/// Ref: Linux input-event-codes.h
|
||||
pub struct InputProp : u8{
|
||||
/// Needs a pointer
|
||||
const POINTER = 1 << 0;
|
||||
/// Direct input devices
|
||||
const DIRECT = 1 << 1;
|
||||
/// Has button(s) under pad
|
||||
const BUTTONPAD = 1 << 2;
|
||||
/// Touch rectangle only
|
||||
const SEMI_MT = 1 << 3;
|
||||
/// Softbuttons at top of pad
|
||||
const TOPBUTTONPAD = 1 << 4;
|
||||
/// Is a pointing stick
|
||||
const POINTING_STICK = 1 << 5;
|
||||
/// Has accelerometer
|
||||
const ACCELEROMETER = 1 << 6;
|
||||
}
|
||||
}
|
||||
|
||||
pub const SYN: u8 = 0x00;
|
||||
pub const KEY: u8 = 0x01;
|
||||
pub const REL: u8 = 0x02;
|
||||
pub const ABS: u8 = 0x03;
|
||||
pub const MSC: u8 = 0x04;
|
||||
pub const SW: u8 = 0x05;
|
||||
pub const LED: u8 = 0x11;
|
||||
pub const SND: u8 = 0x12;
|
||||
pub const REP: u8 = 0x14;
|
||||
pub const FF: u8 = 0x15;
|
||||
pub const PWR: u8 = 0x16;
|
||||
pub const FF_STATUS: u8 = 0x17;
|
||||
|
||||
const QUEUE_SIZE: u16 = 64;
|
||||
|
||||
/// Virtual human interface devices such as keyboards, mice and tablets.
|
||||
///
|
||||
/// An instance of the virtio device represents one such input device.
|
||||
/// Device behavior mirrors that of the evdev layer in Linux,
|
||||
/// making pass-through implementations on top of evdev easy.
|
||||
pub struct InputDevice {
|
||||
config: SafePtr<VirtioInputConfig, IoMem>,
|
||||
event_queue: SpinLock<VirtQueue>,
|
||||
status_queue: VirtQueue,
|
||||
event_buf: SpinLock<Box<[VirtioInputEvent; QUEUE_SIZE as usize]>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
callbacks: SpinLock<Vec<Arc<dyn Fn(InputEvent) + Send + Sync + 'static>>>,
|
||||
transport: Box<dyn VirtioTransport>,
|
||||
}
|
||||
|
||||
impl InputDevice {
|
||||
/// Create a new VirtIO-Input driver.
|
||||
/// msix_vector_left should at least have one element or n elements where n is the virtqueue amount
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let mut event_buf = Box::new([VirtioInputEvent::default(); QUEUE_SIZE as usize]);
|
||||
let mut event_queue = VirtQueue::new(QUEUE_EVENT, QUEUE_SIZE, transport.as_mut())
|
||||
.expect("create event virtqueue failed");
|
||||
let status_queue = VirtQueue::new(QUEUE_STATUS, QUEUE_SIZE, transport.as_mut())
|
||||
.expect("create status virtqueue failed");
|
||||
|
||||
for (i, event) in event_buf.as_mut().iter_mut().enumerate() {
|
||||
// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
let token = event_queue.add_buf(&[], &[event.as_bytes_mut()]);
|
||||
match token {
|
||||
Ok(value) => {
|
||||
assert_eq!(value, i as u16);
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(VirtioDeviceError::QueueUnknownError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut device = Self {
|
||||
config: VirtioInputConfig::new(transport.as_mut()),
|
||||
event_queue: SpinLock::new(event_queue),
|
||||
status_queue,
|
||||
event_buf: SpinLock::new(event_buf),
|
||||
transport,
|
||||
callbacks: SpinLock::new(Vec::new()),
|
||||
};
|
||||
|
||||
let mut raw_name: [u8; 128] = [0; 128];
|
||||
device.query_config_select(InputConfigSelect::IdName, 0, &mut raw_name);
|
||||
let name = String::from_utf8(raw_name.to_vec()).unwrap();
|
||||
info!("Virtio input device name:{}", name);
|
||||
|
||||
let mut prop: [u8; 128] = [0; 128];
|
||||
device.query_config_select(InputConfigSelect::PropBits, 0, &mut prop);
|
||||
let input_prop = InputProp::from_bits(prop[0]).unwrap();
|
||||
debug!("input device prop:{:?}", input_prop);
|
||||
|
||||
fn handle_input(_: &TrapFrame) {
|
||||
debug!("Handle Virtio input interrupt");
|
||||
let device = aster_input::get_device(super::DEVICE_NAME).unwrap();
|
||||
device.handle_irq().unwrap();
|
||||
}
|
||||
|
||||
fn config_space_change(_: &TrapFrame) {
|
||||
debug!("input device config space change");
|
||||
}
|
||||
|
||||
device
|
||||
.transport
|
||||
.register_cfg_callback(Box::new(config_space_change))
|
||||
.unwrap();
|
||||
device
|
||||
.transport
|
||||
.register_queue_callback(QUEUE_EVENT, Box::new(handle_input), false)
|
||||
.unwrap();
|
||||
|
||||
device.transport.finish_init();
|
||||
|
||||
aster_input::register_device(super::DEVICE_NAME.to_string(), Arc::new(device));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pop the pending event.
|
||||
pub fn pop_pending_event(&self) -> Option<VirtioInputEvent> {
|
||||
let mut lock = self.event_queue.lock();
|
||||
if let Ok((token, _)) = lock.pop_used() {
|
||||
if token >= QUEUE_SIZE {
|
||||
return None;
|
||||
}
|
||||
let event = &mut self.event_buf.lock()[token as usize];
|
||||
// requeue
|
||||
// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
if let Ok(new_token) = lock.add_buf(&[], &[event.as_bytes_mut()]) {
|
||||
// This only works because nothing happen between `pop_used` and `add` that affects
|
||||
// the list of free descriptors in the queue, so `add` reuses the descriptor which
|
||||
// was just freed by `pop_used`.
|
||||
assert_eq!(new_token, token);
|
||||
return Some(*event);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Query a specific piece of information by `select` and `subsel`, and write
|
||||
/// result to `out`, return the result size.
|
||||
pub fn query_config_select(&self, select: InputConfigSelect, subsel: u8, out: &mut [u8]) -> u8 {
|
||||
field_ptr!(&self.config, VirtioInputConfig, select)
|
||||
.write(&(select as u8))
|
||||
.unwrap();
|
||||
field_ptr!(&self.config, VirtioInputConfig, subsel)
|
||||
.write(&subsel)
|
||||
.unwrap();
|
||||
let size = field_ptr!(&self.config, VirtioInputConfig, size)
|
||||
.read()
|
||||
.unwrap();
|
||||
let data: [u8; 128] = field_ptr!(&self.config, VirtioInputConfig, data)
|
||||
.read()
|
||||
.unwrap();
|
||||
out[..size as usize].copy_from_slice(&data[..size as usize]);
|
||||
size
|
||||
}
|
||||
|
||||
/// Negotiate features for the device specified bits 0~23
|
||||
pub(crate) fn negotiate_features(features: u64) -> u64 {
|
||||
assert_eq!(features, 0);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl aster_input::InputDevice for InputDevice {
|
||||
fn handle_irq(&self) -> Option<()> {
|
||||
// one interrupt may contains serval input, so it should loop
|
||||
loop {
|
||||
let Some(event) = self.pop_pending_event() else {
|
||||
return Some(());
|
||||
};
|
||||
match event.event_type {
|
||||
0 => return Some(()),
|
||||
// Keyboard
|
||||
1 => {}
|
||||
// TODO: Support mouse device.
|
||||
_ => continue,
|
||||
}
|
||||
let status = match event.value {
|
||||
1 => KeyStatus::Pressed,
|
||||
0 => KeyStatus::Released,
|
||||
_ => return Some(()),
|
||||
};
|
||||
let event = InputEvent::KeyBoard(Key::try_from(event.code).unwrap(), status);
|
||||
info!("Input Event:{:?}", event);
|
||||
|
||||
let callbacks = self.callbacks.lock();
|
||||
for callback in callbacks.iter() {
|
||||
callback.call((event,));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_callbacks(&self, function: &'static (dyn Fn(InputEvent) + Send + Sync)) {
|
||||
self.callbacks.lock().push(Arc::new(function))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for InputDevice {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("InputDevice")
|
||||
.field("config", &self.config)
|
||||
.field("event_queue", &self.event_queue)
|
||||
.field("status_queue", &self.status_queue)
|
||||
.field("event_buf", &self.event_buf)
|
||||
.field("transport", &self.transport)
|
||||
.finish()
|
||||
}
|
||||
}
|
117
kernel/comps/virtio/src/device/input/mod.rs
Normal file
117
kernel/comps/virtio/src/device/input/mod.rs
Normal file
@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Modified from input.rs in virtio-drivers project
|
||||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2022-2023 Ant Group
|
||||
// Copyright (c) 2019-2020 rCore Developers
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
pub mod device;
|
||||
use aster_frame::io_mem::IoMem;
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use pod::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
pub static DEVICE_NAME: &str = "Virtio-Input";
|
||||
|
||||
/// Select value used for [`VirtIOInput::query_config_select()`].
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum InputConfigSelect {
|
||||
/// Returns the name of the device, in u.string. subsel is zero.
|
||||
IdName = 0x01,
|
||||
/// Returns the serial number of the device, in u.string. subsel is zero.
|
||||
IdSerial = 0x02,
|
||||
/// Returns ID information of the device, in u.ids. subsel is zero.
|
||||
IdDevids = 0x03,
|
||||
/// Returns input properties of the device, in u.bitmap. subsel is zero.
|
||||
/// Individual bits in the bitmap correspond to INPUT_PROP_* constants used
|
||||
/// by the underlying evdev implementation.
|
||||
PropBits = 0x10,
|
||||
/// subsel specifies the event type using EV_* constants in the underlying
|
||||
/// evdev implementation. If size is non-zero the event type is supported
|
||||
/// and a bitmap of supported event codes is returned in u.bitmap. Individual
|
||||
/// bits in the bitmap correspond to implementation-defined input event codes,
|
||||
/// for example keys or pointing device axes.
|
||||
EvBits = 0x11,
|
||||
/// subsel specifies the absolute axis using ABS_* constants in the underlying
|
||||
/// evdev implementation. Information about the axis will be returned in u.abs.
|
||||
AbsInfo = 0x12,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioInputConfig {
|
||||
/// write only
|
||||
select: u8,
|
||||
/// write only
|
||||
subsel: u8,
|
||||
/// read only
|
||||
size: u8,
|
||||
_reversed: [u8; 5],
|
||||
/// read only
|
||||
data: [u8; 128],
|
||||
}
|
||||
|
||||
impl VirtioInputConfig {
|
||||
pub(self) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
struct AbsInfo {
|
||||
min: u32,
|
||||
max: u32,
|
||||
fuzz: u32,
|
||||
flat: u32,
|
||||
res: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
struct DevIDs {
|
||||
bustype: u16,
|
||||
vendor: u16,
|
||||
product: u16,
|
||||
version: u16,
|
||||
}
|
||||
|
||||
/// Both queues use the same `virtio_input_event` struct. `type`, `code` and `value`
|
||||
/// are filled according to the Linux input layer (evdev) interface.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, Pod)]
|
||||
pub struct VirtioInputEvent {
|
||||
/// Event type.
|
||||
pub event_type: u16,
|
||||
/// Event code.
|
||||
pub code: u16,
|
||||
/// Event value.
|
||||
pub value: u32,
|
||||
}
|
||||
|
||||
const QUEUE_EVENT: u16 = 0;
|
||||
const QUEUE_STATUS: u16 = 1;
|
55
kernel/comps/virtio/src/device/mod.rs
Normal file
55
kernel/comps/virtio/src/device/mod.rs
Normal file
@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use int_to_c_enum::TryFromInt;
|
||||
|
||||
use crate::queue::QueueError;
|
||||
|
||||
pub mod block;
|
||||
pub mod console;
|
||||
pub mod input;
|
||||
pub mod network;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, TryFromInt)]
|
||||
#[repr(u8)]
|
||||
pub enum VirtioDeviceType {
|
||||
Invalid = 0,
|
||||
Network = 1,
|
||||
Block = 2,
|
||||
Console = 3,
|
||||
Entropy = 4,
|
||||
TraditionalMemoryBalloon = 5,
|
||||
IoMemory = 6,
|
||||
Rpmsg = 7,
|
||||
ScsiHost = 8,
|
||||
Transport9P = 9,
|
||||
Mac80211Wlan = 10,
|
||||
RprocSerial = 11,
|
||||
VirtioCAIF = 12,
|
||||
MemoryBalloon = 13,
|
||||
GPU = 16,
|
||||
Timer = 17,
|
||||
Input = 18,
|
||||
Socket = 19,
|
||||
Crypto = 20,
|
||||
SignalDistribution = 21,
|
||||
Pstore = 22,
|
||||
IOMMU = 23,
|
||||
Memory = 24,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VirtioDeviceError {
|
||||
/// queues amount do not match the requirement
|
||||
/// first element is actual value, second element is expect value
|
||||
QueuesAmountDoNotMatch(u16, u16),
|
||||
/// unknown error of queue
|
||||
QueueUnknownError,
|
||||
/// The input virtio capability list contains invalid element
|
||||
CapabilityListError,
|
||||
}
|
||||
|
||||
impl From<QueueError> for VirtioDeviceError {
|
||||
fn from(_: QueueError) -> Self {
|
||||
VirtioDeviceError::QueueUnknownError
|
||||
}
|
||||
}
|
80
kernel/comps/virtio/src/device/network/config.rs
Normal file
80
kernel/comps/virtio/src/device/network/config.rs
Normal file
@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_frame::io_mem::IoMem;
|
||||
use aster_network::EthernetAddr;
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use bitflags::bitflags;
|
||||
use pod::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
bitflags! {
|
||||
/// Virtio Net Feature bits.
|
||||
pub struct NetworkFeatures: u64 {
|
||||
const VIRTIO_NET_F_CSUM = 1 << 0; // Device handles packets with partial checksum.
|
||||
const VIRTIO_NET_F_GUEST_CSUM = 1 << 1; // Driver handles packets with partial checksum
|
||||
const VIRTIO_NET_F_CTRL_GUEST_OFFLOADS = 1 << 2;// Control channel offloads reconfiguration support
|
||||
const VIRTIO_NET_F_MTU = 1 << 3; // Device maximum MTU reporting is supported
|
||||
const VIRTIO_NET_F_MAC = 1 << 5; // Device has given MAC address.
|
||||
const VIRTIO_NET_F_GUEST_TSO4 = 1 << 7; // Driver can receive TSOv4.
|
||||
const VIRTIO_NET_F_GUEST_TSO6 = 1 <<8; // Driver can receive TSOv6.
|
||||
const VIRTIO_NET_F_GUEST_ECN = 1 << 9; // Driver can receive TSO with ECN.
|
||||
const VIRTIO_NET_F_GUEST_UFO = 1 << 10; // Driver can receive UFO.
|
||||
const VIRTIO_NET_F_HOST_TSO4 = 1 << 11; // Device can receive TSOv4.
|
||||
const VIRTIO_NET_F_HOST_TSO6 = 1 <<12; // Device can receive TSOv6.
|
||||
const VIRTIO_NET_F_HOST_ECN = 1 << 13; // Device can receive TSO with ECN.
|
||||
const VIRTIO_NET_F_HOST_UFO = 1 << 14; // Device can receive UFO.
|
||||
const VIRTIO_NET_F_MRG_RXBUF = 1 << 15; // Driver can merge receive buffers.
|
||||
const VIRTIO_NET_F_STATUS = 1 << 16; // Configuration status field is available.
|
||||
const VIRTIO_NET_F_CTRL_VQ = 1 << 17; // Control channel is available.
|
||||
const VIRTIO_NET_F_CTRL_RX = 1 << 18; // Control channel RX mode support.
|
||||
const VIRTIO_NET_F_CTRL_VLAN = 1 << 19; // Control channel VLAN filtering.
|
||||
const VIRTIO_NET_F_EXTRA = 1 << 20; //
|
||||
const VIRTIO_NET_F_GUEST_ANNOUNCE = 1 << 21; // Driver can send gratuitous packets.
|
||||
const VIRTIO_NET_F_MQ = 1 << 22; // Device supports multiqueue with automatic receive steering.
|
||||
const VIRTIO_NET_F_CTRL_MAC_ADDR = 1 << 23; // Set MAC address through control channel.
|
||||
// const VIRTIO_NET_F_HOST_USO = 1 << 56; // Device can receive USO packets.
|
||||
// const VIRTIO_NET_F_HASH_REPORT = 1 << 57; // Device can report per-packet hash value and a type of calculated hash.
|
||||
// const VIRTIO_NET_F_GUEST_HDRLEN = 1 << 59; // Driver can provide the exact hdr_len value. Device benefits from knowing the exact header length.
|
||||
// const VIRTIO_NET_F_RSS = 1 << 60; // Device supports RSS (receive-side scaling) with Toeplitz hash calculation and configurable hash parameters for receive steering.
|
||||
// const VIRTIO_NET_F_RSC_EXT = 1 << 61; // DevicecanprocessduplicatedACKsandreportnumberofcoalescedseg- ments and duplicated ACKs.
|
||||
// const VIRTIO_NET_F_STANDBY = 1 << 62; // Device may act as a standby for a primary device with the same MAC address.
|
||||
// const VIRTIO_NET_F_SPEED_DUPLEX = 1 << 63; // Device reports speed and duplex.
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkFeatures {
|
||||
pub fn support_features() -> Self {
|
||||
NetworkFeatures::VIRTIO_NET_F_MAC | NetworkFeatures::VIRTIO_NET_F_STATUS
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
#[derive(Pod)]
|
||||
pub struct Status: u16 {
|
||||
const VIRTIO_NET_S_LINK_UP = 1;
|
||||
const VIRTIO_NET_S_ANNOUNCE = 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioNetConfig {
|
||||
pub mac: EthernetAddr,
|
||||
pub status: Status,
|
||||
max_virtqueue_pairs: u16,
|
||||
mtu: u16,
|
||||
speed: u32,
|
||||
duplex: u8,
|
||||
rss_max_key_size: u8,
|
||||
rss_max_indirection_table_length: u16,
|
||||
supported_hash_types: u32,
|
||||
}
|
||||
|
||||
impl VirtioNetConfig {
|
||||
pub(super) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
}
|
||||
}
|
222
kernel/comps/virtio/src/device/network/device.rs
Normal file
222
kernel/comps/virtio/src/device/network/device.rs
Normal file
@ -0,0 +1,222 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
|
||||
use core::{fmt::Debug, hint::spin_loop, mem::size_of};
|
||||
|
||||
use aster_frame::{offset_of, sync::SpinLock, trap::TrapFrame};
|
||||
use aster_network::{
|
||||
buffer::{RxBuffer, TxBuffer},
|
||||
AnyNetworkDevice, EthernetAddr, NetDeviceIrqHandler, VirtioNetError,
|
||||
};
|
||||
use aster_util::{field_ptr, slot_vec::SlotVec};
|
||||
use log::debug;
|
||||
use pod::Pod;
|
||||
use smoltcp::phy::{DeviceCapabilities, Medium};
|
||||
|
||||
use super::{config::VirtioNetConfig, header::VirtioNetHdr};
|
||||
use crate::{
|
||||
device::{network::config::NetworkFeatures, VirtioDeviceError},
|
||||
queue::{QueueError, VirtQueue},
|
||||
transport::VirtioTransport,
|
||||
};
|
||||
|
||||
pub struct NetworkDevice {
|
||||
config: VirtioNetConfig,
|
||||
mac_addr: EthernetAddr,
|
||||
send_queue: VirtQueue,
|
||||
recv_queue: VirtQueue,
|
||||
rx_buffers: SlotVec<RxBuffer>,
|
||||
callbacks: Vec<Box<dyn NetDeviceIrqHandler>>,
|
||||
transport: Box<dyn VirtioTransport>,
|
||||
}
|
||||
|
||||
impl NetworkDevice {
|
||||
pub(crate) fn negotiate_features(device_features: u64) -> u64 {
|
||||
let device_features = NetworkFeatures::from_bits_truncate(device_features);
|
||||
let supported_features = NetworkFeatures::support_features();
|
||||
let network_features = device_features & supported_features;
|
||||
debug!("{:?}", network_features);
|
||||
network_features.bits()
|
||||
}
|
||||
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let virtio_net_config = VirtioNetConfig::new(transport.as_mut());
|
||||
let features = NetworkFeatures::from_bits_truncate(Self::negotiate_features(
|
||||
transport.device_features(),
|
||||
));
|
||||
debug!("virtio_net_config = {:?}", virtio_net_config);
|
||||
debug!("features = {:?}", features);
|
||||
let mac_addr = field_ptr!(&virtio_net_config, VirtioNetConfig, mac)
|
||||
.read()
|
||||
.unwrap();
|
||||
let status = field_ptr!(&virtio_net_config, VirtioNetConfig, status)
|
||||
.read()
|
||||
.unwrap();
|
||||
debug!("mac addr = {:x?}, status = {:?}", mac_addr, status);
|
||||
let mut recv_queue = VirtQueue::new(QUEUE_RECV, QUEUE_SIZE, transport.as_mut())
|
||||
.expect("creating recv queue fails");
|
||||
let send_queue = VirtQueue::new(QUEUE_SEND, QUEUE_SIZE, transport.as_mut())
|
||||
.expect("create send queue fails");
|
||||
|
||||
let mut rx_buffers = SlotVec::new();
|
||||
for i in 0..QUEUE_SIZE {
|
||||
let mut rx_buffer = RxBuffer::new(RX_BUFFER_LEN, size_of::<VirtioNetHdr>());
|
||||
// FIEME: Replace rx_buffer with VM segment-based data structure to use dma mapping.
|
||||
let token = recv_queue.add_buf(&[], &[rx_buffer.buf_mut()])?;
|
||||
assert_eq!(i, token);
|
||||
assert_eq!(rx_buffers.put(rx_buffer) as u16, i);
|
||||
}
|
||||
|
||||
if recv_queue.should_notify() {
|
||||
debug!("notify receive queue");
|
||||
recv_queue.notify();
|
||||
}
|
||||
let mut device = Self {
|
||||
config: virtio_net_config.read().unwrap(),
|
||||
mac_addr,
|
||||
send_queue,
|
||||
recv_queue,
|
||||
rx_buffers,
|
||||
transport,
|
||||
callbacks: Vec::new(),
|
||||
};
|
||||
device.transport.finish_init();
|
||||
/// Interrupt handler if network device config space changes
|
||||
fn config_space_change(_: &TrapFrame) {
|
||||
debug!("network device config space change");
|
||||
}
|
||||
|
||||
/// Interrupt handler if network device receives some packet
|
||||
fn handle_network_event(_: &TrapFrame) {
|
||||
aster_network::handle_recv_irq(super::DEVICE_NAME);
|
||||
}
|
||||
|
||||
device
|
||||
.transport
|
||||
.register_cfg_callback(Box::new(config_space_change))
|
||||
.unwrap();
|
||||
device
|
||||
.transport
|
||||
.register_queue_callback(QUEUE_RECV, Box::new(handle_network_event), false)
|
||||
.unwrap();
|
||||
|
||||
aster_network::register_device(
|
||||
super::DEVICE_NAME.to_string(),
|
||||
Arc::new(SpinLock::new(Box::new(device))),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a rx buffer to recv queue
|
||||
/// FIEME: Replace rx_buffer with VM segment-based data structure to use dma mapping.
|
||||
fn add_rx_buffer(&mut self, mut rx_buffer: RxBuffer) -> Result<(), VirtioNetError> {
|
||||
let token = self
|
||||
.recv_queue
|
||||
.add_buf(&[], &[rx_buffer.buf_mut()])
|
||||
.map_err(queue_to_network_error)?;
|
||||
assert!(self.rx_buffers.put_at(token as usize, rx_buffer).is_none());
|
||||
if self.recv_queue.should_notify() {
|
||||
self.recv_queue.notify();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive a packet from network. If packet is ready, returns a RxBuffer containing the packet.
|
||||
/// Otherwise, return NotReady error.
|
||||
fn receive(&mut self) -> Result<RxBuffer, VirtioNetError> {
|
||||
let (token, len) = self.recv_queue.pop_used().map_err(queue_to_network_error)?;
|
||||
debug!("receive packet: token = {}, len = {}", token, len);
|
||||
let mut rx_buffer = self
|
||||
.rx_buffers
|
||||
.remove(token as usize)
|
||||
.ok_or(VirtioNetError::WrongToken)?;
|
||||
rx_buffer.set_packet_len(len as usize);
|
||||
// FIXME: Ideally, we can reuse the returned buffer without creating new buffer.
|
||||
// But this requires locking device to be compatible with smoltcp interface.
|
||||
let new_rx_buffer = RxBuffer::new(RX_BUFFER_LEN, size_of::<VirtioNetHdr>());
|
||||
self.add_rx_buffer(new_rx_buffer)?;
|
||||
Ok(rx_buffer)
|
||||
}
|
||||
|
||||
/// Send a packet to network. Return until the request completes.
|
||||
/// FIEME: Replace tx_buffer with VM segment-based data structure to use dma mapping.
|
||||
fn send(&mut self, tx_buffer: TxBuffer) -> Result<(), VirtioNetError> {
|
||||
let header = VirtioNetHdr::default();
|
||||
let token = self
|
||||
.send_queue
|
||||
.add_buf(&[header.as_bytes(), tx_buffer.buf()], &[])
|
||||
.map_err(queue_to_network_error)?;
|
||||
|
||||
if self.send_queue.should_notify() {
|
||||
self.send_queue.notify();
|
||||
}
|
||||
// Wait until the buffer is used
|
||||
while !self.send_queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
// Pop out the buffer, so we can reuse the send queue further
|
||||
let (pop_token, _) = self.send_queue.pop_used().map_err(queue_to_network_error)?;
|
||||
debug_assert!(pop_token == token);
|
||||
if pop_token != token {
|
||||
return Err(VirtioNetError::WrongToken);
|
||||
}
|
||||
debug!("send packet succeeds");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_to_network_error(err: QueueError) -> VirtioNetError {
|
||||
match err {
|
||||
QueueError::NotReady => VirtioNetError::NotReady,
|
||||
QueueError::WrongToken => VirtioNetError::WrongToken,
|
||||
_ => VirtioNetError::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyNetworkDevice for NetworkDevice {
|
||||
fn mac_addr(&self) -> EthernetAddr {
|
||||
self.mac_addr
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> DeviceCapabilities {
|
||||
let mut caps = DeviceCapabilities::default();
|
||||
caps.max_transmission_unit = 1536;
|
||||
caps.max_burst_size = Some(1);
|
||||
caps.medium = Medium::Ethernet;
|
||||
caps
|
||||
}
|
||||
|
||||
fn can_receive(&self) -> bool {
|
||||
self.recv_queue.can_pop()
|
||||
}
|
||||
|
||||
fn can_send(&self) -> bool {
|
||||
self.send_queue.available_desc() >= 2
|
||||
}
|
||||
|
||||
fn receive(&mut self) -> Result<RxBuffer, VirtioNetError> {
|
||||
self.receive()
|
||||
}
|
||||
|
||||
fn send(&mut self, tx_buffer: TxBuffer) -> Result<(), VirtioNetError> {
|
||||
self.send(tx_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NetworkDevice {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("NetworkDevice")
|
||||
.field("config", &self.config)
|
||||
.field("mac_addr", &self.mac_addr)
|
||||
.field("send_queue", &self.send_queue)
|
||||
.field("recv_queue", &self.recv_queue)
|
||||
.field("transport", &self.transport)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
const QUEUE_RECV: u16 = 0;
|
||||
const QUEUE_SEND: u16 = 1;
|
||||
|
||||
const QUEUE_SIZE: u16 = 64;
|
||||
const RX_BUFFER_LEN: usize = 4096;
|
46
kernel/comps/virtio/src/device/network/header.rs
Normal file
46
kernel/comps/virtio/src/device/network/header.rs
Normal file
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use bitflags::bitflags;
|
||||
use int_to_c_enum::TryFromInt;
|
||||
use pod::Pod;
|
||||
|
||||
pub const VIRTIO_NET_HDR_LEN: usize = core::mem::size_of::<VirtioNetHdr>();
|
||||
|
||||
/// VirtioNet header precedes each packet
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy, Pod)]
|
||||
pub struct VirtioNetHdr {
|
||||
flags: Flags,
|
||||
gso_type: u8,
|
||||
hdr_len: u16,
|
||||
gso_size: u16,
|
||||
csum_start: u16,
|
||||
csum_offset: u16,
|
||||
num_buffers: u16, // Only if PCI is modern or VIRTIO_NET_F_MRG_RXBUF negotiated
|
||||
// hash_value: u32, // Only if VIRTIO_NET_F_HASH_REPORT negotiated
|
||||
// hash_report: u16, // Only if VIRTIO_NET_F_HASH_REPORT negotiated
|
||||
// padding_reserved: u16, // Only if VIRTIO_NET_F_HASH_REPORT negotiated
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
#[derive(Default, Pod)]
|
||||
pub struct Flags: u8 {
|
||||
const VIRTIO_NET_HDR_F_NEEDS_CSUM = 1;
|
||||
const VIRTIO_NET_HDR_F_DATA_VALID = 2;
|
||||
const VIRTIO_NET_HDR_F_RSC_INFO = 4;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Default, Debug, Clone, Copy, TryFromInt)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum GsoType {
|
||||
#[default]
|
||||
VIRTIO_NET_HDR_GSO_NONE = 0,
|
||||
VIRTIO_NET_HDR_GSO_TCPV4 = 1,
|
||||
VIRTIO_NET_HDR_GSO_UDP = 3,
|
||||
VIRTIO_NET_HDR_GSO_TCPV6 = 4,
|
||||
VIRTIO_NET_HDR_GSO_UDP_L4 = 5,
|
||||
VIRTIO_NET_HDR_GSO_ECN = 0x80,
|
||||
}
|
7
kernel/comps/virtio/src/device/network/mod.rs
Normal file
7
kernel/comps/virtio/src/device/network/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
pub mod header;
|
||||
|
||||
pub static DEVICE_NAME: &str = "Virtio-Net";
|
121
kernel/comps/virtio/src/lib.rs
Normal file
121
kernel/comps/virtio/src/lib.rs
Normal file
@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! The virtio of Asterinas.
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![allow(dead_code)]
|
||||
#![feature(fn_traits)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use component::{init_component, ComponentInitError};
|
||||
use device::{
|
||||
block::device::BlockDevice, console::device::ConsoleDevice, input::device::InputDevice,
|
||||
network::device::NetworkDevice, VirtioDeviceType,
|
||||
};
|
||||
use log::{error, warn};
|
||||
use transport::{mmio::VIRTIO_MMIO_DRIVER, pci::VIRTIO_PCI_DRIVER, DeviceStatus};
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
pub mod device;
|
||||
pub mod queue;
|
||||
mod transport;
|
||||
|
||||
#[init_component]
|
||||
fn virtio_component_init() -> Result<(), ComponentInitError> {
|
||||
// Find all devices and register them to the corresponding crate
|
||||
transport::init();
|
||||
while let Some(mut transport) = pop_device_transport() {
|
||||
// Reset device
|
||||
transport.set_device_status(DeviceStatus::empty()).unwrap();
|
||||
// Set to acknowledge
|
||||
transport
|
||||
.set_device_status(DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER)
|
||||
.unwrap();
|
||||
// negotiate features
|
||||
negotiate_features(&mut transport);
|
||||
|
||||
// change to features ok status
|
||||
transport
|
||||
.set_device_status(
|
||||
DeviceStatus::ACKNOWLEDGE | DeviceStatus::DRIVER | DeviceStatus::FEATURES_OK,
|
||||
)
|
||||
.unwrap();
|
||||
let device_type = transport.device_type();
|
||||
let res = match transport.device_type() {
|
||||
VirtioDeviceType::Block => BlockDevice::init(transport),
|
||||
VirtioDeviceType::Input => InputDevice::init(transport),
|
||||
VirtioDeviceType::Network => NetworkDevice::init(transport),
|
||||
VirtioDeviceType::Console => ConsoleDevice::init(transport),
|
||||
_ => {
|
||||
warn!("[Virtio]: Found unimplemented device:{:?}", device_type);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
if res.is_err() {
|
||||
error!(
|
||||
"[Virtio]: Device initialization error:{:?}, device type:{:?}",
|
||||
res, device_type
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
if let Some(device) = VIRTIO_MMIO_DRIVER.get().unwrap().pop_device_transport() {
|
||||
return Some(Box::new(device));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn negotiate_features(transport: &mut Box<dyn VirtioTransport>) {
|
||||
let features = transport.device_features();
|
||||
let mask = ((1u64 << 24) - 1) | (((1u64 << 24) - 1) << 50);
|
||||
let device_specified_features = features & mask;
|
||||
let device_support_features = match transport.device_type() {
|
||||
VirtioDeviceType::Network => NetworkDevice::negotiate_features(device_specified_features),
|
||||
VirtioDeviceType::Block => BlockDevice::negotiate_features(device_specified_features),
|
||||
VirtioDeviceType::Input => InputDevice::negotiate_features(device_specified_features),
|
||||
VirtioDeviceType::Console => ConsoleDevice::negotiate_features(device_specified_features),
|
||||
_ => device_specified_features,
|
||||
};
|
||||
let mut support_feature = Feature::from_bits_truncate(features);
|
||||
support_feature.remove(Feature::RING_EVENT_IDX);
|
||||
transport
|
||||
.set_driver_features(features & (support_feature.bits | device_support_features))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// all device features, bits 0~23 and 50~63 are sepecified by device.
|
||||
/// if using this struct to translate u64, use from_bits_truncate function instead of from_bits
|
||||
///
|
||||
struct Feature: u64 {
|
||||
|
||||
// device independent
|
||||
const NOTIFY_ON_EMPTY = 1 << 24; // legacy
|
||||
const ANY_LAYOUT = 1 << 27; // legacy
|
||||
const RING_INDIRECT_DESC = 1 << 28;
|
||||
const RING_EVENT_IDX = 1 << 29;
|
||||
const UNUSED = 1 << 30; // legacy
|
||||
const VERSION_1 = 1 << 32; // detect legacy
|
||||
|
||||
// since virtio v1.1
|
||||
const ACCESS_PLATFORM = 1 << 33;
|
||||
const RING_PACKED = 1 << 34;
|
||||
const IN_ORDER = 1 << 35;
|
||||
const ORDER_PLATFORM = 1 << 36;
|
||||
const SR_IOV = 1 << 37;
|
||||
const NOTIFICATION_DATA = 1 << 38;
|
||||
const NOTIF_CONFIG_DATA = 1 << 39;
|
||||
const RING_RESET = 1 << 40;
|
||||
}
|
||||
}
|
525
kernel/comps/virtio/src/queue.rs
Normal file
525
kernel/comps/virtio/src/queue.rs
Normal file
@ -0,0 +1,525 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Virtqueue
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::{
|
||||
mem::size_of,
|
||||
sync::atomic::{fence, Ordering},
|
||||
};
|
||||
|
||||
use aster_frame::{
|
||||
io_mem::IoMem,
|
||||
offset_of,
|
||||
vm::{DmaCoherent, VmAllocOptions, VmReader, VmWriter},
|
||||
};
|
||||
use aster_rights::{Dup, TRightSet, TRights, Write};
|
||||
use aster_util::{field_ptr, safe_ptr::SafePtr};
|
||||
use bitflags::bitflags;
|
||||
use log::debug;
|
||||
use pod::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueueError {
|
||||
InvalidArgs,
|
||||
BufferTooSmall,
|
||||
NotReady,
|
||||
AlreadyUsed,
|
||||
WrongToken,
|
||||
}
|
||||
|
||||
/// The mechanism for bulk data transport on virtio devices.
|
||||
///
|
||||
/// Each device can have zero or more virtqueues.
|
||||
#[derive(Debug)]
|
||||
pub struct VirtQueue {
|
||||
/// Descriptor table
|
||||
descs: Vec<SafePtr<Descriptor, DmaCoherent>>,
|
||||
/// Available ring
|
||||
avail: SafePtr<AvailRing, DmaCoherent>,
|
||||
/// Used ring
|
||||
used: SafePtr<UsedRing, DmaCoherent>,
|
||||
/// point to notify address
|
||||
notify: SafePtr<u32, IoMem>,
|
||||
|
||||
/// The index of queue
|
||||
queue_idx: u32,
|
||||
/// The size of the queue.
|
||||
///
|
||||
/// This is both the number of descriptors, and the number of slots in the available and used
|
||||
/// rings.
|
||||
queue_size: u16,
|
||||
/// The number of used queues.
|
||||
num_used: u16,
|
||||
/// The head desc index of the free list.
|
||||
free_head: u16,
|
||||
/// the index of the next avail ring index
|
||||
avail_idx: u16,
|
||||
/// last service used index
|
||||
last_used_idx: u16,
|
||||
}
|
||||
|
||||
impl VirtQueue {
|
||||
/// Create a new VirtQueue.
|
||||
pub(crate) fn new(
|
||||
idx: u16,
|
||||
size: u16,
|
||||
transport: &mut dyn VirtioTransport,
|
||||
) -> Result<Self, QueueError> {
|
||||
if !size.is_power_of_two() {
|
||||
return Err(QueueError::InvalidArgs);
|
||||
}
|
||||
|
||||
let (descriptor_ptr, avail_ring_ptr, used_ring_ptr) = if transport.is_legacy_version() {
|
||||
// FIXME: How about pci legacy?
|
||||
// Currently, we use one VmFrame to place the descriptors and avaliable rings, one VmFrame 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 (seg1, seg2) = {
|
||||
let continue_segment = VmAllocOptions::new(2)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()
|
||||
.unwrap();
|
||||
let seg1 = continue_segment.range(0..1);
|
||||
let seg2 = continue_segment.range(1..2);
|
||||
(seg1, seg2)
|
||||
};
|
||||
let desc_frame_ptr: SafePtr<Descriptor, DmaCoherent> =
|
||||
SafePtr::new(DmaCoherent::map(seg1, true).unwrap(), 0);
|
||||
let mut avail_frame_ptr: SafePtr<AvailRing, DmaCoherent> =
|
||||
desc_frame_ptr.clone().cast();
|
||||
avail_frame_ptr.byte_add(desc_size);
|
||||
let used_frame_ptr: SafePtr<UsedRing, DmaCoherent> =
|
||||
SafePtr::new(DmaCoherent::map(seg2, true).unwrap(), 0);
|
||||
(desc_frame_ptr, avail_frame_ptr, used_frame_ptr)
|
||||
} else {
|
||||
if size > 256 {
|
||||
return Err(QueueError::InvalidArgs);
|
||||
}
|
||||
(
|
||||
SafePtr::new(
|
||||
DmaCoherent::map(
|
||||
VmAllocOptions::new(1)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()
|
||||
.unwrap(),
|
||||
true,
|
||||
)
|
||||
.unwrap(),
|
||||
0,
|
||||
),
|
||||
SafePtr::new(
|
||||
DmaCoherent::map(
|
||||
VmAllocOptions::new(1)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()
|
||||
.unwrap(),
|
||||
true,
|
||||
)
|
||||
.unwrap(),
|
||||
0,
|
||||
),
|
||||
SafePtr::new(
|
||||
DmaCoherent::map(
|
||||
VmAllocOptions::new(1)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()
|
||||
.unwrap(),
|
||||
true,
|
||||
)
|
||||
.unwrap(),
|
||||
0,
|
||||
),
|
||||
)
|
||||
};
|
||||
debug!("queue_desc start paddr:{:x?}", descriptor_ptr.paddr());
|
||||
debug!("queue_driver start paddr:{:x?}", avail_ring_ptr.paddr());
|
||||
debug!("queue_device start paddr:{:x?}", used_ring_ptr.paddr());
|
||||
|
||||
transport
|
||||
.set_queue(idx, size, &descriptor_ptr, &avail_ring_ptr, &used_ring_ptr)
|
||||
.unwrap();
|
||||
let mut descs = Vec::with_capacity(size as usize);
|
||||
descs.push(descriptor_ptr);
|
||||
for i in 0..size as usize {
|
||||
let mut desc = descs.get(i).unwrap().clone();
|
||||
desc.add(1);
|
||||
descs.push(desc);
|
||||
}
|
||||
|
||||
let notify = transport.get_notify_ptr(idx).unwrap();
|
||||
// Link descriptors together.
|
||||
for i in 0..(size - 1) {
|
||||
let temp = descs.get(i as usize).unwrap();
|
||||
field_ptr!(temp, Descriptor, next).write(&(i + 1)).unwrap();
|
||||
}
|
||||
field_ptr!(&avail_ring_ptr, AvailRing, flags)
|
||||
.write(&(0u16))
|
||||
.unwrap();
|
||||
Ok(VirtQueue {
|
||||
descs,
|
||||
avail: avail_ring_ptr,
|
||||
used: used_ring_ptr,
|
||||
notify,
|
||||
queue_size: size,
|
||||
queue_idx: idx as u32,
|
||||
num_used: 0,
|
||||
free_head: 0,
|
||||
avail_idx: 0,
|
||||
last_used_idx: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Add buffers to the virtqueue, return a token. **This function will be removed in the future.**
|
||||
///
|
||||
/// Ref: linux virtio_ring.c virtqueue_add
|
||||
pub fn add_buf(&mut self, inputs: &[&[u8]], outputs: &[&mut [u8]]) -> Result<u16, QueueError> {
|
||||
// FIXME: use `DmaSteam` for inputs and outputs. Now because the upper device driver lacks the
|
||||
// ability to safely construct DmaStream from slice, slice is still used here.
|
||||
// pub fn add(
|
||||
// &mut self,
|
||||
// inputs: &[&DmaStream],
|
||||
// outputs: &[&mut DmaStream],
|
||||
// ) -> Result<u16, QueueError> {
|
||||
|
||||
if inputs.is_empty() && outputs.is_empty() {
|
||||
return Err(QueueError::InvalidArgs);
|
||||
}
|
||||
if inputs.len() + outputs.len() + self.num_used as usize > self.queue_size as usize {
|
||||
return Err(QueueError::BufferTooSmall);
|
||||
}
|
||||
|
||||
// allocate descriptors from free list
|
||||
let head = self.free_head;
|
||||
let mut last = self.free_head;
|
||||
for input in inputs.iter() {
|
||||
let desc = &self.descs[self.free_head as usize];
|
||||
set_buf_slice(&desc.borrow_vm().restrict::<TRights![Write, Dup]>(), input);
|
||||
field_ptr!(desc, Descriptor, flags)
|
||||
.write(&DescFlags::NEXT)
|
||||
.unwrap();
|
||||
last = self.free_head;
|
||||
self.free_head = field_ptr!(desc, Descriptor, next).read().unwrap();
|
||||
}
|
||||
for output in outputs.iter() {
|
||||
let desc = &mut self.descs[self.free_head as usize];
|
||||
set_buf_slice(&desc.borrow_vm().restrict::<TRights![Write, Dup]>(), output);
|
||||
field_ptr!(desc, Descriptor, flags)
|
||||
.write(&(DescFlags::NEXT | DescFlags::WRITE))
|
||||
.unwrap();
|
||||
last = self.free_head;
|
||||
self.free_head = field_ptr!(desc, Descriptor, next).read().unwrap();
|
||||
}
|
||||
// set last_elem.next = NULL
|
||||
{
|
||||
let desc = &mut self.descs[last as usize];
|
||||
let mut flags: DescFlags = field_ptr!(desc, Descriptor, flags).read().unwrap();
|
||||
flags.remove(DescFlags::NEXT);
|
||||
field_ptr!(desc, Descriptor, flags).write(&flags).unwrap();
|
||||
}
|
||||
self.num_used += (inputs.len() + outputs.len()) as u16;
|
||||
|
||||
let avail_slot = self.avail_idx & (self.queue_size - 1);
|
||||
|
||||
{
|
||||
let ring_ptr: SafePtr<[u16; 64], &DmaCoherent> =
|
||||
field_ptr!(&self.avail, AvailRing, ring);
|
||||
let mut ring_slot_ptr = ring_ptr.cast::<u16>();
|
||||
ring_slot_ptr.add(avail_slot as usize);
|
||||
ring_slot_ptr.write(&head).unwrap();
|
||||
}
|
||||
// write barrier
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
// increase head of avail ring
|
||||
self.avail_idx = self.avail_idx.wrapping_add(1);
|
||||
field_ptr!(&self.avail, AvailRing, idx)
|
||||
.write(&self.avail_idx)
|
||||
.unwrap();
|
||||
|
||||
fence(Ordering::SeqCst);
|
||||
Ok(head)
|
||||
}
|
||||
|
||||
/// Add VmReader/VmWriter to the virtqueue, return a token.
|
||||
///
|
||||
/// Ref: linux virtio_ring.c virtqueue_add
|
||||
pub fn add_vm(
|
||||
&mut self,
|
||||
inputs: &[&VmReader],
|
||||
outputs: &[&VmWriter],
|
||||
) -> Result<u16, QueueError> {
|
||||
if inputs.is_empty() && outputs.is_empty() {
|
||||
return Err(QueueError::InvalidArgs);
|
||||
}
|
||||
if inputs.len() + outputs.len() + self.num_used as usize > self.queue_size as usize {
|
||||
return Err(QueueError::BufferTooSmall);
|
||||
}
|
||||
|
||||
// allocate descriptors from free list
|
||||
let head = self.free_head;
|
||||
let mut last = self.free_head;
|
||||
for input in inputs.iter() {
|
||||
let desc = &self.descs[self.free_head as usize];
|
||||
set_buf_reader(&desc.borrow_vm().restrict::<TRights![Write, Dup]>(), input);
|
||||
field_ptr!(desc, Descriptor, flags)
|
||||
.write(&DescFlags::NEXT)
|
||||
.unwrap();
|
||||
last = self.free_head;
|
||||
self.free_head = field_ptr!(desc, Descriptor, next).read().unwrap();
|
||||
}
|
||||
for output in outputs.iter() {
|
||||
let desc = &mut self.descs[self.free_head as usize];
|
||||
set_buf_writer(&desc.borrow_vm().restrict::<TRights![Write, Dup]>(), output);
|
||||
field_ptr!(desc, Descriptor, flags)
|
||||
.write(&(DescFlags::NEXT | DescFlags::WRITE))
|
||||
.unwrap();
|
||||
last = self.free_head;
|
||||
self.free_head = field_ptr!(desc, Descriptor, next).read().unwrap();
|
||||
}
|
||||
// set last_elem.next = NULL
|
||||
{
|
||||
let desc = &mut self.descs[last as usize];
|
||||
let mut flags: DescFlags = field_ptr!(desc, Descriptor, flags).read().unwrap();
|
||||
flags.remove(DescFlags::NEXT);
|
||||
field_ptr!(desc, Descriptor, flags).write(&flags).unwrap();
|
||||
}
|
||||
self.num_used += (inputs.len() + outputs.len()) as u16;
|
||||
|
||||
let avail_slot = self.avail_idx & (self.queue_size - 1);
|
||||
|
||||
{
|
||||
let ring_ptr: SafePtr<[u16; 64], &DmaCoherent> =
|
||||
field_ptr!(&self.avail, AvailRing, ring);
|
||||
let mut ring_slot_ptr = ring_ptr.cast::<u16>();
|
||||
ring_slot_ptr.add(avail_slot as usize);
|
||||
ring_slot_ptr.write(&head).unwrap();
|
||||
}
|
||||
// write barrier
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
// increase head of avail ring
|
||||
self.avail_idx = self.avail_idx.wrapping_add(1);
|
||||
field_ptr!(&self.avail, AvailRing, idx)
|
||||
.write(&self.avail_idx)
|
||||
.unwrap();
|
||||
|
||||
fence(Ordering::SeqCst);
|
||||
Ok(head)
|
||||
}
|
||||
|
||||
/// Whether there is a used element that can pop.
|
||||
pub fn can_pop(&self) -> bool {
|
||||
self.last_used_idx != field_ptr!(&self.used, UsedRing, idx).read().unwrap()
|
||||
}
|
||||
|
||||
/// The number of free descriptors.
|
||||
pub fn available_desc(&self) -> usize {
|
||||
(self.queue_size - self.num_used) as usize
|
||||
}
|
||||
|
||||
/// Recycle descriptors in the list specified by head.
|
||||
///
|
||||
/// This will push all linked descriptors at the front of the free list.
|
||||
fn recycle_descriptors(&mut self, mut head: u16) {
|
||||
let origin_free_head = self.free_head;
|
||||
self.free_head = head;
|
||||
let last_free_head = if head == 0 {
|
||||
self.queue_size - 1
|
||||
} else {
|
||||
head - 1
|
||||
};
|
||||
let temp_desc = &mut self.descs[last_free_head as usize];
|
||||
field_ptr!(temp_desc, Descriptor, next)
|
||||
.write(&head)
|
||||
.unwrap();
|
||||
loop {
|
||||
let desc = &mut self.descs[head as usize];
|
||||
let flags: DescFlags = field_ptr!(desc, Descriptor, flags).read().unwrap();
|
||||
self.num_used -= 1;
|
||||
if flags.contains(DescFlags::NEXT) {
|
||||
head = field_ptr!(desc, Descriptor, next).read().unwrap();
|
||||
} else {
|
||||
field_ptr!(desc, Descriptor, next)
|
||||
.write(&origin_free_head)
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a token from device used buffers, return (token, len).
|
||||
///
|
||||
/// Ref: linux virtio_ring.c virtqueue_get_buf_ctx
|
||||
pub fn pop_used(&mut self) -> Result<(u16, u32), QueueError> {
|
||||
if !self.can_pop() {
|
||||
return Err(QueueError::NotReady);
|
||||
}
|
||||
// read barrier
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
let last_used_slot = self.last_used_idx & (self.queue_size - 1);
|
||||
let element_ptr = {
|
||||
let mut ptr = self.used.borrow_vm();
|
||||
ptr.byte_add(offset_of!(UsedRing, ring) as usize + last_used_slot as usize * 8);
|
||||
ptr.cast::<UsedElem>()
|
||||
};
|
||||
let index = field_ptr!(&element_ptr, UsedElem, id).read().unwrap();
|
||||
let len = field_ptr!(&element_ptr, UsedElem, len).read().unwrap();
|
||||
|
||||
self.recycle_descriptors(index as u16);
|
||||
self.last_used_idx = self.last_used_idx.wrapping_add(1);
|
||||
|
||||
Ok((index as u16, len))
|
||||
}
|
||||
|
||||
/// If the given token is next on the device used queue, pops it and returns the total buffer
|
||||
/// length which was used (written) by the device.
|
||||
///
|
||||
/// Ref: linux virtio_ring.c virtqueue_get_buf_ctx
|
||||
pub fn pop_used_with_token(&mut self, token: u16) -> Result<u32, QueueError> {
|
||||
if !self.can_pop() {
|
||||
return Err(QueueError::NotReady);
|
||||
}
|
||||
// read barrier
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
let last_used_slot = self.last_used_idx & (self.queue_size - 1);
|
||||
let element_ptr = {
|
||||
let mut ptr = self.used.borrow_vm();
|
||||
ptr.byte_add(offset_of!(UsedRing, ring) as usize + last_used_slot as usize * 8);
|
||||
ptr.cast::<UsedElem>()
|
||||
};
|
||||
let index = field_ptr!(&element_ptr, UsedElem, id).read().unwrap();
|
||||
let len = field_ptr!(&element_ptr, UsedElem, len).read().unwrap();
|
||||
|
||||
if index as u16 != token {
|
||||
return Err(QueueError::WrongToken);
|
||||
}
|
||||
|
||||
self.recycle_descriptors(index as u16);
|
||||
self.last_used_idx = self.last_used_idx.wrapping_add(1);
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
/// Return size of the queue.
|
||||
pub fn size(&self) -> u16 {
|
||||
self.queue_size
|
||||
}
|
||||
|
||||
/// whether the driver should notify the device
|
||||
pub fn should_notify(&self) -> bool {
|
||||
// read barrier
|
||||
fence(Ordering::SeqCst);
|
||||
let flags = field_ptr!(&self.used, UsedRing, flags).read().unwrap();
|
||||
flags & 0x0001u16 == 0u16
|
||||
}
|
||||
|
||||
/// notify that there are available rings
|
||||
pub fn notify(&mut self) {
|
||||
self.notify.write(&self.queue_idx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, align(16))]
|
||||
#[derive(Debug, Default, Copy, Clone, Pod)]
|
||||
pub struct Descriptor {
|
||||
addr: u64,
|
||||
len: u32,
|
||||
flags: DescFlags,
|
||||
next: u16,
|
||||
}
|
||||
|
||||
type DescriptorPtr<'a> = SafePtr<Descriptor, &'a DmaCoherent, TRightSet<TRights![Dup, Write]>>;
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn set_buf_slice(desc_ptr: &DescriptorPtr, buf: &[u8]) {
|
||||
// FIXME: use `DmaSteam` for buf. Now because the upper device driver lacks the
|
||||
// ability to safely construct DmaStream from slice, slice is still used here.
|
||||
let va = buf.as_ptr() as usize;
|
||||
let pa = aster_frame::vm::vaddr_to_paddr(va).unwrap();
|
||||
field_ptr!(desc_ptr, Descriptor, addr)
|
||||
.write(&(pa as u64))
|
||||
.unwrap();
|
||||
field_ptr!(desc_ptr, Descriptor, len)
|
||||
.write(&(buf.len() as u32))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn set_buf_reader(desc_ptr: &DescriptorPtr, reader: &VmReader) {
|
||||
let va = reader.cursor() as usize;
|
||||
let pa = aster_frame::vm::vaddr_to_paddr(va).unwrap();
|
||||
field_ptr!(desc_ptr, Descriptor, addr)
|
||||
.write(&(pa as u64))
|
||||
.unwrap();
|
||||
field_ptr!(desc_ptr, Descriptor, len)
|
||||
.write(&(reader.remain() as u32))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn set_buf_writer(desc_ptr: &DescriptorPtr, writer: &VmWriter) {
|
||||
let va = writer.cursor() as usize;
|
||||
let pa = aster_frame::vm::vaddr_to_paddr(va).unwrap();
|
||||
field_ptr!(desc_ptr, Descriptor, addr)
|
||||
.write(&(pa as u64))
|
||||
.unwrap();
|
||||
field_ptr!(desc_ptr, Descriptor, len)
|
||||
.write(&(writer.avail() as u32))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Descriptor flags
|
||||
#[derive(Pod, Default)]
|
||||
#[repr(C)]
|
||||
struct DescFlags: u16 {
|
||||
const NEXT = 1;
|
||||
const WRITE = 2;
|
||||
const INDIRECT = 4;
|
||||
}
|
||||
}
|
||||
|
||||
/// The driver uses the available ring to offer buffers to the device:
|
||||
/// each ring entry refers to the head of a descriptor chain.
|
||||
/// It is only written by the driver and read by the device.
|
||||
#[repr(C, align(2))]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
pub struct AvailRing {
|
||||
flags: u16,
|
||||
/// A driver MUST NOT decrement the idx.
|
||||
idx: u16,
|
||||
ring: [u16; 64], // actual size: queue_size
|
||||
used_event: u16, // unused
|
||||
}
|
||||
|
||||
/// The used ring is where the device returns buffers once it is done with them:
|
||||
/// it is only written to by the device, and read by the driver.
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
pub struct UsedRing {
|
||||
// the flag in UsedRing
|
||||
flags: u16,
|
||||
// the next index of the used element in ring array
|
||||
idx: u16,
|
||||
ring: [UsedElem; 64], // actual size: queue_size
|
||||
avail_event: u16, // unused
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, Pod)]
|
||||
pub struct UsedElem {
|
||||
id: u32,
|
||||
len: u32,
|
||||
}
|
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