Refactor project structure

This commit is contained in:
Zhang Junyang
2024-02-27 16:40:16 +08:00
committed by Tate, Hongliang Tian
parent bd878dd1c9
commit e3c227ae06
474 changed files with 77 additions and 77 deletions

View 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 _,
}
}
}

View 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)
}
}

View 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)
}
}

View 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");
}

View File

@ -0,0 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
pub mod config;
pub mod device;
pub static DEVICE_NAME: &str = "Virtio-Console";

View 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()
}
}

View 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;

View 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
}
}

View 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)
}
}

View 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;

View 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,
}

View 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";

View 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;
}
}

View 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,
}

View 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(())
}
}

View 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)
}
}

View 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 queues Descriptor Area 64 bit long physical address. **Write-only**
pub queue_desc_low: u32,
pub queue_desc_high: u32,
__r7: [u8; 8],
/// Virtual queues Driver Area 64 bit long physical address. **Write-only**
pub queue_driver_low: u32,
pub queue_driver_high: u32,
__r8: [u8; 8],
/// Virtual queues 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()
}
}

View 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());
}

View 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()
}
}

View 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
/// didnt 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
/// cant recover.
const DEVICE_NEEDS_RESET = 64;
}
}
pub fn init() {
virtio_pci_init();
virtio_mmio_init();
}

View 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,
}
}
}

View 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,
)
}
}

View 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 }),
})
}
}

View 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)
}
}

View 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());
}

View 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()))
}
}