mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-30 10:53:58 +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";
|
Reference in New Issue
Block a user