Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
112: New VirtioNet Driver and Extended Virtio Infrastructure (PackedVq) r=stlankes a=mustermeiszer

The following pull request includes:

- Extention of the Virtio PCI functionality
- Extention of the VirtioNet driver
- Implementation of the Packed Virtqueue
- Implementation of the Split Virtqueue (Most functionality of the new Interface)

Must be seen/used together with the PR in the rusty-hermit repository [(PR rusty-hermit)](hermit-os/hermit-rs#71)

## Extention of the PCI functionality

Virtio defines multiple ways to facilitate communication between devices and their drivers. One way is to use a PCI bus. Based on the existing PCI functionality this implementation extends the existing functionality of the kernel. The extention is focuses solely on the Virtio specific aspect of the existing kernel. 
The Virtio standard defines multiple configuration structures, which allow to configure and use a virtio device over a PCI bus. Given a virtio driver already posses the existing ```struct PciAdapter``` a driver can use the following call to gain access to all Virtio specific configuration structures.

```rust
// The PciAdapter struct can be obtained from the kernels pci code.
// &adapter is of type PciAdapter

let capability_collection: UniCapsColl = crate::drivers::virtio::transport::pci::map_caps(&adapter);

// Where UniCapsColl is structured as follows
struct UniCapsColl struct UniCapsColl {
    com_cfg_list: Vec<ComCfg>,
    notif_cfg_list: Vec<NotifCfg>,
    isr_stat_list: Vec<IsrStatus>,
    pci_cfg_acc_list: Vec<PciCfgAlt>,
    sh_mem_cfg_list: Vec<ShMemCfg>,
    dev_cfg_list: Vec<PciCap>
}
```

If the respective vector is empty, the device does not define the respective configuration structure. Allthough all devices MUST define at least a ```struct ComCfg```.  As devices are allowed to define multiple instances of each structure the `UniCapsColl` caries a vector of them. The device specifies a descending preference for structures of the same type and the vectors store the structures in this descending preference (i.e. the configuration structure the device prefers the most will be placed at the first entry of the vector, and so forth). 

One field of the struct must be noted seperately. The field `dev_cfg_list` contains the possibility to access device specific configuration structures via the given `PciCap`.  As those configuration structures are different for each device drivers must map those structures individually. The code provides the following helper function, to do this: 
```rust
// A virtio device specific configuration struct
struct NetDevCfg {
    ...
}

// pci_cap is an item from the dev_cfg_list
let net_dev_cfg: &'static NetDevCfg = match crate::drivers::virtio::transport::pci::map_dev_cfg::<NetDevCfg>(&pci_cap).unwrap();
```


## Extention of the VirtioNet Driver
The new driver is a complete rewrite of the existing driver. It Adds functionality to
-  Adding features (A minimal feature set is defined, if this set is not provided by the device, the driver aborts initalization)
   - The driver tries to reduce it's features down to the minimal set, before aborting. If a feature match is found before that point, the driver will uses this feature set.
- Error management. The new driver catches errors and tries to resolve them. Therefore the code does return a lot of Result<...,...>'s.
- Control functionality. Allthough no syscalls are currently implemented the driver provides the infrastructure to use the control functionality of the device. Through a seperated "Control Virtqueue" the driver is able to send the network device commands. Including among others, VLAN filtering, receive filtering, and mac filtering. 

## Implementation of the Packed Virtqueue
The implementation in this pull request tries to provide a simple-to-use interface, while hiding the complexity of the virtqueue. 
Virtio specifies two kinds of so called virtqueues, both existing for the actual data transfer between device and driver. The kernel already has an implementation of the "split virtqueue" and this pull request implements the second virtqueue, the "packed virtqueue". In order to allow a unified usage of both virtqueues in drivers, the implementation provides a common interface for both queues (Currently the interface is only implemented for the packed virtqueue, but in the future the split virtqueue can be ported to this interface, so users who want to use a split virtqueue currently have to use the split virtqueues interface). A cutout from the unified interface is listed below.

```rust
pub enum Virtq {
    Packed(PackedVq),
    Split(SplitVq), // Currently unimplemented
}

impl Virtq {
    /// Returns a new instance of the specified Virtq variant.
    /// The structs ComCfg and NotifCfg can be obtained via the above shown function from the virito-pci code.
    pub fn new(com_cfg: &mut ComCfg, notif_cfg: &NotifCfg, size: VqSize, vq_type: VqType, index: VqIndex, feats: u64) -> Self {
        match vq_type {
            VqType::Packed => match PackedVq::new(com_cfg, notif_cfg, size, index, feats) {
                Ok(packed_vq) => Virtq::Packed(packed_vq),
                Err(vq_error) => panic!("Currently panics if queue fails to be created")
            },
            VqType::Split => unimplemented!()
        }
    
    /// This function creates a BufferToken
    pub fn prep_buffer(&self, rc_self: Rc<Virtq>, send: Option<BuffSpec>, recv: Option<BuffSpec>) -> Result<BufferToken, VirtqError> {
        match self {
            Packed(vq) => vq.prep_buffer(rc: Rc<Virtq>, send: Option<BuffSpec>, recv: Option<BuffSpec>),
            Split(vq) => unimplemented!(),
    }
    
    // Enables interrupts (i.e. signal device to send interrupts).
    pub fn enable_interrupts(&self) {
        match self {
            Packed(vq) => vq.enable_interrupt(),
            Split(vq) => vq.enable_interrupt(),
    }

   .
   .
   .
}
```
The actual Virtqueue specification can be found in the resources below. 

In the following I will provide a short overview, how to use the virtq. The main advantage at the given approach is, that drivers do not need to handle the complexity of the queues and that most memory management is in the hand of the queue. 

```rust
// Create a new virtqueue, assuming we already obtained the configuration structs from the pci code
let type = VqType::Packed;
let size = VqSize::from(256);
let index = VqIndex(0);
// features is a u64, which bitwise indicates the features negotiated with the device.

let vq = Rc::new(Virtq::new(com_cfg, notif_cfg, size, type, index, features));

// Buffers in the virtqueue can contain write-only and read-only areas. 
// Each BufferToken created via the following function will be regarded as a token for a single transfer.
//
// We specify which kind of buffer, we want.
// In this example we create a 1500 bytes large read-only buffer for the device.
// and two write-only buffers for the device, of size 10 bytes and 1000 bytes.
let send_spec = BuffSpec::Single(Bytes::new(1500).unwrap());
let size_deff = [Bytes::new(10).unwrap(), Bytes::new(1000).unwrap()];
let recv_spec = BuffSpec::Multiple(&size_deff);

// We need to provide the queue with a Rc::clone of the virtqueue as every token holds on, in order to provide memory
// safety in cases where Transfers are dropped by the driver before the actual transfer in the queue is finished.
// This is needed, as the tokens are owners of the memory, which is accessed by device. 
// Therefore the virtqueue will take care of holding early dropped tokens until the transfer is finished.
let buff_tkn: BufferToken = vq.prep_buffer(Rc::clone(&vq), Some(send_spec), Some(recv_spec)).unwrap();

// If we want we can write data into the read-only buffers now. 
// As we do not know all use-cases, we can also write into the write-only buffers, if needed.
// But here we only write into the read-only buffer.
// The "data" that is written, needs to implement a AsSliceU8 trait, in order to be writable. 
// Afterwards we provide the BufferToken and receive a TransferToken. This intermediate step is necessary
// as the queue does provide batch-transfer of TransferTokens. Then the we dispatch the TransferToken.
// After this step the actual transfer is placed in the virtqueue and can be accessed by the device.
let transfer = buff_tkn.write(Some(data), None)) :Result<BufferToken, VirtqError>
    .unwrap() :BufferToken
    .provide() :TransferToken
    .dispatch() :Transfer

// Transfer can be polled. The poll function returns true, if the transfer is finished. All actions on the transfer will return
// an error if executed before the transfer.poll() function returns true.
if transfer.poll() {
    // If this is true, the transfer is finished and might be dropped or reused
    let buff_tkn = transfer.reuse() :Result<BufferToken, VirtqError>
        .unwrap() :BufferToken
        // At this point we can use the BufferToken again for a transfer. 
        // Also we can restrict the size of a BufferTokens memories. Upon reuse the size of 
       // underlying will be restored to their size at initalization. 
} else { 
    // This means, the transfer is ongoing. 
    // We can safely drop the transfer and it will be returned to the virtqueue
    drop(transfer)
}
```

For further details please look at the resources below and the actual code, which is carefully documented.

## Resources

- The specification this codes functionality is based on, can be found here: [Virtio Spec v1.1.](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html)
- My masterthesis: [MA_Schulz_Frederik.pdf](https://github.com/hermitcore/libhermit-rs/files/5746925/MA_Schulz_Frederik.pdf)


Co-authored-by: mustermeiszer <[email protected]>
  • Loading branch information
bors[bot] and mustermeiszer authored Jan 4, 2021
2 parents 6cc1b7e + f32cd89 commit b7bb664
Show file tree
Hide file tree
Showing 22 changed files with 11,051 additions and 119 deletions.
6 changes: 0 additions & 6 deletions src/arch/x86_64/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ mod smp_boot_code;
pub mod systemtime;
#[cfg(feature = "vga")]
mod vga;
#[cfg(feature = "pci")]
pub mod virtio;
#[cfg(feature = "pci")]
pub mod virtio_fs;
#[cfg(feature = "pci")]
pub mod virtio_net;

#[cfg(not(test))]
global_asm!(include_str!("start.s"));
Expand Down
47 changes: 40 additions & 7 deletions src/arch/x86_64/kernel/pci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
// copied, modified, or distributed except according to those terms.

use crate::arch::x86_64::kernel::pci_ids::{CLASSES, VENDORS};
use crate::arch::x86_64::kernel::virtio;
use crate::arch::x86_64::kernel::virtio_fs::VirtioFsDriver;
use crate::arch::x86_64::kernel::virtio_net::VirtioNetDriver;
use crate::arch::x86_64::mm::{PhysAddr, VirtAddr};
use crate::collections::irqsave;
use crate::drivers::net::virtio_net::VirtioNetDriver as VnetDrv;
use crate::drivers::virtio::depr::virtio_fs::VirtioFsDriver;
use crate::drivers::virtio::depr::virtio_net::VirtioNetDriver;
use crate::drivers::virtio::transport::pci as pci_virtio;
use crate::drivers::virtio::transport::pci::VirtioDriver;
use crate::synch::spinlock::SpinlockIrqSave;
use crate::x86::io::*;
use alloc::vec::Vec;
Expand Down Expand Up @@ -127,12 +129,13 @@ pub struct MemoryBar {
pub enum PciDriver<'a> {
VirtioFs(SpinlockIrqSave<VirtioFsDriver<'a>>),
VirtioNet(SpinlockIrqSave<VirtioNetDriver<'a>>),
VirtioNetNew(SpinlockIrqSave<VnetDrv>),
}

impl<'a> PciDriver<'a> {
fn get_network_driver(&self) -> Option<&SpinlockIrqSave<VirtioNetDriver<'a>>> {
fn get_network_driver(&self) -> Option<&SpinlockIrqSave<VnetDrv>> {
match self {
Self::VirtioNet(drv) => Some(drv),
Self::VirtioNetNew(drv) => Some(drv),
_ => None,
}
}
Expand All @@ -150,7 +153,7 @@ pub fn register_driver(drv: PciDriver<'static>) {
}
}

pub fn get_network_driver() -> Option<&'static SpinlockIrqSave<VirtioNetDriver<'static>>> {
pub fn get_network_driver() -> Option<&'static SpinlockIrqSave<VnetDrv>> {
unsafe { PCI_DRIVERS.iter().find_map(|drv| drv.get_network_driver()) }
}

Expand Down Expand Up @@ -445,6 +448,8 @@ impl fmt::Display for PciAdapter {
}
}

/// Returns the value (indicated by bus, device and register) of the pci
/// configuration space.
pub fn read_config(bus: u8, device: u8, register: u32) -> u32 {
let address =
PCI_CONFIG_ADDRESS_ENABLE | u32::from(bus) << 16 | u32::from(device) << 11 | register;
Expand Down Expand Up @@ -508,7 +513,18 @@ pub fn init_drivers() {
"Found virtio device with device id 0x{:x}",
adapter.device_id
);
virtio::init_virtio_device(adapter);

// This weird match and back to match and then match driver is needed
// in order to let the compiler know, that we are giving him a static driver struct.
match pci_virtio::init_device(&adapter) {
Ok(drv) => match drv {
VirtioDriver::Network(drv) => {
register_driver(PciDriver::VirtioNetNew(SpinlockIrqSave::new(drv)))
}
VirtioDriver::FileSystem => (), // Filesystem is pushed to the driver struct inside init_device()
},
Err(_) => (), // could have an info which driver failed
}
}
});
}
Expand All @@ -522,3 +538,20 @@ pub fn print_information() {

infofooter!();
}

/// A module containg PCI specifc errors
///
/// Errors include...
pub mod error {
/// An enum of PciErrors
/// typically carrying the device's id as an u16.
#[derive(Debug)]
pub enum PciError {
General(u16),
NoBar(u16),
NoCapPtr(u16),
BadCapPtr(u16),
NoBarForCap(u16),
NoVirtioCaps(u16),
}
}
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub const DEFAULT_STACK_SIZE: usize = 32_768;
pub const USER_STACK_SIZE: usize = 1_048_576;

#[allow(dead_code)]
pub const VIRTIO_MAX_QUEUE_SIZE: u16 = 256;
pub const VIRTIO_MAX_QUEUE_SIZE: u16 = 2048;

/// See https://github.com/facebook/folly/blob/1b5288e6eea6df074758f877c849b6e73bbb9fbb/folly/lang/Align.h#L107 for details
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
Expand Down
43 changes: 42 additions & 1 deletion src/drivers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,50 @@
// Copyright (c) 2019 Stefan Lankes, RWTH Aachen University
// 2020 Frederik Schulz, RWTH Aachen University
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//
//! A module containing hermit-rs driver, hermit-rs driver trait and driver specific errors.
//!
//! The module contains ...
#[cfg(not(feature = "newlib"))]
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// UNCOMMENTED FOR CORRECT USE STATEMENT; IS THIS CORRECT?
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//#[cfg(not(feature = "newlib"))]
pub mod net;

#[cfg(feature = "pci")]
pub mod virtio;

/// A common error module for drivers.
/// [DriverError](enums.drivererror.html) values will be
/// passed on to higher layers.
#[cfg(feature = "pci")]
pub mod error {
use crate::drivers::virtio::error::VirtioError;
use core::fmt;

#[derive(Debug)]
pub enum DriverError {
InitVirtioDevFail(VirtioError),
}

impl From<VirtioError> for DriverError {
fn from(err: VirtioError) -> Self {
DriverError::InitVirtioDevFail(err)
}
}

impl fmt::Display for DriverError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
DriverError::InitVirtioDevFail(ref err) => {
write!(f, "Virtio driver failed: {:?}", err)
}
}
}
}
}
6 changes: 5 additions & 1 deletion src/drivers/net/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// Copyright (c) 2019 Stefan Lankes, RWTH Aachen University
// 2020 Frederik Schulz, RWTH Aachen University
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

#[cfg(feature = "pci")]
pub mod virtio_net;

use crate::arch::kernel::percore::*;
use crate::scheduler::task::TaskHandle;
use crate::synch::semaphore::*;
Expand All @@ -26,7 +30,7 @@ fn set_polling_mode(value: bool) {
if POLLING.swap(value, Ordering::SeqCst) != value {
#[cfg(feature = "pci")]
if let Some(driver) = crate::arch::kernel::pci::get_network_driver() {
driver.lock().set_polling_mode(value);
driver.lock().set_polling_mode(value)
}

// wakeup network thread to sleep for longer time
Expand Down
Loading

0 comments on commit b7bb664

Please sign in to comment.