From 78686a7e70c9203ba29e425929c393fbb7eca208 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Thu, 18 Jul 2024 15:09:56 +0800 Subject: [PATCH] Initial commit --- .github/workflows/ci.yml | 57 ++++++++ .gitignore | 4 + Cargo.toml | 28 ++++ README.md | 10 ++ axdriver_base/Cargo.toml | 14 ++ axdriver_base/src/lib.rs | 62 +++++++++ axdriver_block/Cargo.toml | 22 +++ axdriver_block/src/bcm2835sdhci.rs | 88 ++++++++++++ axdriver_block/src/lib.rs | 38 ++++++ axdriver_block/src/ramdisk.rs | 100 ++++++++++++++ axdriver_display/Cargo.toml | 15 +++ axdriver_display/src/lib.rs | 59 ++++++++ axdriver_net/Cargo.toml | 22 +++ axdriver_net/src/ixgbe.rs | 162 ++++++++++++++++++++++ axdriver_net/src/lib.rs | 106 +++++++++++++++ axdriver_net/src/net_buf.rs | 208 +++++++++++++++++++++++++++++ axdriver_pci/Cargo.toml | 15 +++ axdriver_pci/src/lib.rs | 53 ++++++++ axdriver_virtio/Cargo.toml | 24 ++++ axdriver_virtio/src/blk.rs | 60 +++++++++ axdriver_virtio/src/gpu.rs | 70 ++++++++++ axdriver_virtio/src/lib.rs | 97 ++++++++++++++ axdriver_virtio/src/net.rs | 193 ++++++++++++++++++++++++++ 23 files changed, 1507 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 axdriver_base/Cargo.toml create mode 100644 axdriver_base/src/lib.rs create mode 100644 axdriver_block/Cargo.toml create mode 100644 axdriver_block/src/bcm2835sdhci.rs create mode 100644 axdriver_block/src/lib.rs create mode 100644 axdriver_block/src/ramdisk.rs create mode 100644 axdriver_display/Cargo.toml create mode 100644 axdriver_display/src/lib.rs create mode 100644 axdriver_net/Cargo.toml create mode 100644 axdriver_net/src/ixgbe.rs create mode 100644 axdriver_net/src/lib.rs create mode 100644 axdriver_net/src/net_buf.rs create mode 100644 axdriver_pci/Cargo.toml create mode 100644 axdriver_pci/src/lib.rs create mode 100644 axdriver_virtio/Cargo.toml create mode 100644 axdriver_virtio/src/blk.rs create mode 100644 axdriver_virtio/src/gpu.rs create mode 100644 axdriver_virtio/src/lib.rs create mode 100644 axdriver_virtio/src/net.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..694b727 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: CI + +on: [push, pull_request] + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + rust-toolchain: [nightly] + targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + toolchain: ${{ matrix.rust-toolchain }} + components: rust-src, clippy, rustfmt + targets: ${{ matrix.targets }} + - name: Check rust version + run: rustc --version --verbose + - name: Check code format + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default + - name: Build + run: cargo build --target ${{ matrix.targets }} --all-features + - name: Unit test + if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} + run: cargo test --target ${{ matrix.targets }} -- --nocapture + + doc: + runs-on: ubuntu-latest + strategy: + fail-fast: false + permissions: + contents: write + env: + default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} + RUSTDOCFLAGS: -Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Build docs + continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} + run: | + for crate in axdriver_base axdriver_block axdriver_net axdriver_display axdriver_pci axdriver_virtio; + do + cargo rustdoc --all-features -p $crate + done + - name: Deploy to Github Pages + if: ${{ github.ref == env.default-branch }} + uses: JamesIves/github-pages-deploy-action@v4 + with: + single-commit: true + branch: gh-pages + folder: target/doc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff78c42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/.vscode +.DS_Store +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..269eab0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[workspace] +resolver = "2" + +members = [ + "axdriver_base", + "axdriver_block", + "axdriver_net", + "axdriver_display", + "axdriver_pci", + "axdriver_virtio", +] + +[workspace.package] +version = "0.1.0" +authors = ["Yuekai Jia "] +license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0" +homepage = "https://github.com/arceos-org/arceos" +documentation = "https://arceos-org.github.io/axdriver_crates" +repository = "https://github.com/arceos-org/axdriver_crates" +categories = ["os", "no-std", "hardware-support"] + +[workspace.dependencies] +axdriver_base = { path = "axdriver_base", version = "0.1" } +axdriver_block = { path = "axdriver_block", version = "0.1" } +axdriver_net = { path = "axdriver_net", version = "0.1" } +axdriver_display = { path = "axdriver_display", version = "0.1" } +axdriver_pci = { path = "axdriver_pci", version = "0.1" } +axdriver_virtio = { path = "axdriver_virtio", version = "0.1" } diff --git a/README.md b/README.md new file mode 100644 index 0000000..b83a763 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# axdriver_crates + +Crates for building device driver subsystems in the `no_std` environment: + +- [axdriver_base](https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_base): Common interfaces for all kinds of device drivers. +- [axdriver_block](https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_block): Common traits and types for block storage drivers. +- [axdriver_net](https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_net): Common traits and types for network device (NIC) drivers. +- [axdriver_display](https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_display): Common traits and types for graphics device drivers. +- [axdriver_pci](https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_pci): Structures and functions for PCI bus operations. +- [axdriver_virtio](https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_virtio): Wrappers of some devices in the [virtio-drivers](https://docs.rs/virtio-drivers) crate, that implement traits in the `axdriver`-series crates. diff --git a/axdriver_base/Cargo.toml b/axdriver_base/Cargo.toml new file mode 100644 index 0000000..29e375e --- /dev/null +++ b/axdriver_base/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "axdriver_base" +edition = "2021" +description = "Common interfaces for all kinds of device drivers" +documentation = "https://arceos-org.github.io/axdriver_crates/axdriver_base" +keywords = ["arceos", "dirver"] +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true + +[dependencies] diff --git a/axdriver_base/src/lib.rs b/axdriver_base/src/lib.rs new file mode 100644 index 0000000..6247b79 --- /dev/null +++ b/axdriver_base/src/lib.rs @@ -0,0 +1,62 @@ +//! Device driver interfaces used by [ArceOS][1]. It provides common traits and +//! types for implementing a device driver. +//! +//! You have to use this crate with the following crates for corresponding +//! device types: +//! +//! - [`axdriver_block`][2]: Common traits for block storage drivers. +//! - [`axdriver_display`][3]: Common traits and types for graphics display drivers. +//! - [`axdriver_net`][4]: Common traits and types for network (NIC) drivers. +//! +//! [1]: https://github.com/arceos-org/arceos +//! [2]: ../axdriver_block/index.html +//! [3]: ../axdriver_display/index.html +//! [4]: ../axdriver_net/index.html + +#![no_std] + +/// All supported device types. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum DeviceType { + /// Block storage device (e.g., disk). + Block, + /// Character device (e.g., serial port). + Char, + /// Network device (e.g., ethernet card). + Net, + /// Graphic display device (e.g., GPU) + Display, +} + +/// The error type for device operation failures. +#[derive(Debug)] +pub enum DevError { + /// An entity already exists. + AlreadyExists, + /// Try again, for non-blocking APIs. + Again, + /// Bad internal state. + BadState, + /// Invalid parameter/argument. + InvalidParam, + /// Input/output error. + Io, + /// Not enough space/cannot allocate memory (DMA). + NoMemory, + /// Device or resource is busy. + ResourceBusy, + /// This operation is unsupported or unimplemented. + Unsupported, +} + +/// A specialized `Result` type for device operations. +pub type DevResult = Result; + +/// Common operations that require all device drivers to implement. +pub trait BaseDriverOps: Send + Sync { + /// The name of the device. + fn device_name(&self) -> &str; + + /// The type of the device. + fn device_type(&self) -> DeviceType; +} diff --git a/axdriver_block/Cargo.toml b/axdriver_block/Cargo.toml new file mode 100644 index 0000000..bfc58ef --- /dev/null +++ b/axdriver_block/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "axdriver_block" +edition = "2021" +description = "Common traits and types for block storage drivers" +documentation = "https://arceos-org.github.io/axdriver_crates/axdriver_block" +keywords = ["arceos", "dirver", "blk"] +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true + +[features] +ramdisk = [] +bcm2835-sdhci = ["dep:bcm2835-sdhci"] +default = [] + +[dependencies] +log = "0.4" +axdriver_base = { workspace = true } +bcm2835-sdhci = { git = "https://github.com/lhw2002426/bcm2835-sdhci.git", rev = "e974f16", optional = true } diff --git a/axdriver_block/src/bcm2835sdhci.rs b/axdriver_block/src/bcm2835sdhci.rs new file mode 100644 index 0000000..365ae7c --- /dev/null +++ b/axdriver_block/src/bcm2835sdhci.rs @@ -0,0 +1,88 @@ +//! SD card driver for raspi4 + +extern crate alloc; +use crate::BlockDriverOps; +use axdriver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; +use bcm2835_sdhci::Bcm2835SDhci::{EmmcCtl, BLOCK_SIZE}; +use bcm2835_sdhci::SDHCIError; + +/// BCM2835 SDHCI driver (Raspberry Pi SD card). +pub struct SDHCIDriver(EmmcCtl); + +impl SDHCIDriver { + /// Initialize the SDHCI driver, returns `Ok` if successful. + pub fn try_new() -> DevResult { + let mut ctrl = EmmcCtl::new(); + if ctrl.init() == 0 { + log::info!("BCM2835 sdhci: successfully initialized"); + Ok(SDHCIDriver(ctrl)) + } else { + log::warn!("BCM2835 sdhci: init failed"); + Err(DevError::Io) + } + } +} + +fn deal_sdhci_err(err: SDHCIError) -> DevError { + match err { + SDHCIError::Io => DevError::Io, + SDHCIError::AlreadyExists => DevError::AlreadyExists, + SDHCIError::Again => DevError::Again, + SDHCIError::BadState => DevError::BadState, + SDHCIError::InvalidParam => DevError::InvalidParam, + SDHCIError::NoMemory => DevError::NoMemory, + SDHCIError::ResourceBusy => DevError::ResourceBusy, + SDHCIError::Unsupported => DevError::Unsupported, + } +} + +impl BaseDriverOps for SDHCIDriver { + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + + fn device_name(&self) -> &str { + "bcm2835_sdhci" + } +} + +impl BlockDriverOps for SDHCIDriver { + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { + if buf.len() < BLOCK_SIZE { + return Err(DevError::InvalidParam); + } + let (prefix, aligned_buf, suffix) = unsafe { buf.align_to_mut::() }; + if !prefix.is_empty() || !suffix.is_empty() { + return Err(DevError::InvalidParam); + } + self.0 + .read_block(block_id as u32, 1, aligned_buf) + .map_err(deal_sdhci_err) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { + if buf.len() < BLOCK_SIZE { + return Err(DevError::Io); + } + let (prefix, aligned_buf, suffix) = unsafe { buf.align_to::() }; + if !prefix.is_empty() || !suffix.is_empty() { + return Err(DevError::InvalidParam); + } + self.0 + .write_block(block_id as u32, 1, aligned_buf) + .map_err(deal_sdhci_err) + } + fn flush(&mut self) -> DevResult { + Ok(()) + } + + #[inline] + fn num_blocks(&self) -> u64 { + self.0.get_block_num() + } + + #[inline] + fn block_size(&self) -> usize { + self.0.get_block_size() + } +} diff --git a/axdriver_block/src/lib.rs b/axdriver_block/src/lib.rs new file mode 100644 index 0000000..f6cddfb --- /dev/null +++ b/axdriver_block/src/lib.rs @@ -0,0 +1,38 @@ +//! Common traits and types for block storage device drivers (i.e. disk). + +#![no_std] +#![feature(doc_auto_cfg)] + +#[cfg(feature = "ramdisk")] +pub mod ramdisk; + +#[cfg(feature = "bcm2835-sdhci")] +pub mod bcm2835sdhci; + +#[doc(no_inline)] +pub use axdriver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; + +/// Operations that require a block storage device driver to implement. +pub trait BlockDriverOps: BaseDriverOps { + /// The number of blocks in this storage device. + /// + /// The total size of the device is `num_blocks() * block_size()`. + fn num_blocks(&self) -> u64; + /// The size of each block in bytes. + fn block_size(&self) -> usize; + + /// Reads blocked data from the given block. + /// + /// The size of the buffer may exceed the block size, in which case multiple + /// contiguous blocks will be read. + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult; + + /// Writes blocked data to the given block. + /// + /// The size of the buffer may exceed the block size, in which case multiple + /// contiguous blocks will be written. + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult; + + /// Flushes the device to write all pending data to the storage. + fn flush(&mut self) -> DevResult; +} diff --git a/axdriver_block/src/ramdisk.rs b/axdriver_block/src/ramdisk.rs new file mode 100644 index 0000000..ffa448e --- /dev/null +++ b/axdriver_block/src/ramdisk.rs @@ -0,0 +1,100 @@ +//! Mock block devices that store data in RAM. + +extern crate alloc; + +use crate::BlockDriverOps; +use alloc::{vec, vec::Vec}; +use axdriver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; + +const BLOCK_SIZE: usize = 512; + +/// A RAM disk that stores data in a vector. +#[derive(Default)] +pub struct RamDisk { + size: usize, + data: Vec, +} + +impl RamDisk { + /// Creates a new RAM disk with the given size hint. + /// + /// The actual size of the RAM disk will be aligned upwards to the block + /// size (512 bytes). + pub fn new(size_hint: usize) -> Self { + let size = align_up(size_hint); + Self { + size, + data: vec![0; size], + } + } + + /// Creates a new RAM disk from the exiting data. + /// + /// The actual size of the RAM disk will be aligned upwards to the block + /// size (512 bytes). + pub fn from(buf: &[u8]) -> Self { + let size = align_up(buf.len()); + let mut data = vec![0; size]; + data[..buf.len()].copy_from_slice(buf); + Self { size, data } + } + + /// Returns the size of the RAM disk in bytes. + pub const fn size(&self) -> usize { + self.size + } +} + +impl BaseDriverOps for RamDisk { + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + + fn device_name(&self) -> &str { + "ramdisk" + } +} + +impl BlockDriverOps for RamDisk { + #[inline] + fn num_blocks(&self) -> u64 { + (self.size / BLOCK_SIZE) as u64 + } + + #[inline] + fn block_size(&self) -> usize { + BLOCK_SIZE + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { + let offset = block_id as usize * BLOCK_SIZE; + if offset + buf.len() > self.size { + return Err(DevError::Io); + } + if buf.len() % BLOCK_SIZE != 0 { + return Err(DevError::InvalidParam); + } + buf.copy_from_slice(&self.data[offset..offset + buf.len()]); + Ok(()) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { + let offset = block_id as usize * BLOCK_SIZE; + if offset + buf.len() > self.size { + return Err(DevError::Io); + } + if buf.len() % BLOCK_SIZE != 0 { + return Err(DevError::InvalidParam); + } + self.data[offset..offset + buf.len()].copy_from_slice(buf); + Ok(()) + } + + fn flush(&mut self) -> DevResult { + Ok(()) + } +} + +const fn align_up(val: usize) -> usize { + (val + BLOCK_SIZE - 1) & !(BLOCK_SIZE - 1) +} diff --git a/axdriver_display/Cargo.toml b/axdriver_display/Cargo.toml new file mode 100644 index 0000000..5546d3c --- /dev/null +++ b/axdriver_display/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "axdriver_display" +edition = "2021" +description = "Common traits and types for graphics device drivers" +documentation = "https://arceos-org.github.io/axdriver_crates/axdriver_display" +keywords = ["arceos", "dirver", "gpu", "framebuffer"] +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true + +[dependencies] +axdriver_base = { workspace = true } diff --git a/axdriver_display/src/lib.rs b/axdriver_display/src/lib.rs new file mode 100644 index 0000000..5bf9a12 --- /dev/null +++ b/axdriver_display/src/lib.rs @@ -0,0 +1,59 @@ +//! Common traits and types for graphics display device drivers. + +#![no_std] + +#[doc(no_inline)] +pub use axdriver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; + +/// The information of the graphics device. +#[derive(Debug, Clone, Copy)] +pub struct DisplayInfo { + /// The visible width. + pub width: u32, + /// The visible height. + pub height: u32, + /// The base virtual address of the framebuffer. + pub fb_base_vaddr: usize, + /// The size of the framebuffer in bytes. + pub fb_size: usize, +} + +/// The framebuffer. +/// +/// It's a special memory buffer that mapped from the device memory. +pub struct FrameBuffer<'a> { + _raw: &'a mut [u8], +} + +impl<'a> FrameBuffer<'a> { + /// Use the given raw pointer and size as the framebuffer. + /// + /// # Safety + /// + /// Caller must insure that the given memory region is valid and accessible. + pub unsafe fn from_raw_parts_mut(ptr: *mut u8, len: usize) -> Self { + Self { + _raw: core::slice::from_raw_parts_mut(ptr, len), + } + } + + /// Use the given slice as the framebuffer. + pub fn from_slice(slice: &'a mut [u8]) -> Self { + Self { _raw: slice } + } +} + +/// Operations that require a graphics device driver to implement. +pub trait DisplayDriverOps: BaseDriverOps { + /// Get the display information. + fn info(&self) -> DisplayInfo; + + /// Get the framebuffer. + fn fb(&self) -> FrameBuffer; + + /// Whether need to flush the framebuffer to the screen. + fn need_flush(&self) -> bool; + + /// Flush framebuffer to the screen. + fn flush(&mut self) -> DevResult; +} diff --git a/axdriver_net/Cargo.toml b/axdriver_net/Cargo.toml new file mode 100644 index 0000000..ef2f209 --- /dev/null +++ b/axdriver_net/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "axdriver_net" +edition = "2021" +authors = ["Yuekai Jia ", "ChengXiang Qi "] +description = "Common traits and types for network device (NIC) drivers" +documentation = "https://arceos-org.github.io/axdriver_crates/axdriver_net" +keywords = ["arceos", "dirver", "nic"] +version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true + +[features] +default = [] +ixgbe = ["dep:ixgbe-driver"] + +[dependencies] +spin = "0.9" +log = "0.4" +axdriver_base = { workspace = true } +ixgbe-driver = { git = "https://github.com/KuangjuX/ixgbe-driver.git", rev = "8e5eb74", optional = true} diff --git a/axdriver_net/src/ixgbe.rs b/axdriver_net/src/ixgbe.rs new file mode 100644 index 0000000..354f079 --- /dev/null +++ b/axdriver_net/src/ixgbe.rs @@ -0,0 +1,162 @@ +use core::convert::From; +use core::{mem::ManuallyDrop, ptr::NonNull}; + +use alloc::{collections::VecDeque, sync::Arc}; +use axdriver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; +use ixgbe_driver::{IxgbeDevice, IxgbeError, IxgbeNetBuf, MemPool, NicDevice}; +pub use ixgbe_driver::{IxgbeHal, PhysAddr, INTEL_82599, INTEL_VEND}; + +use crate::{EthernetAddress, NetBufPtr, NetDriverOps}; + +extern crate alloc; + +const RECV_BATCH_SIZE: usize = 64; +const RX_BUFFER_SIZE: usize = 1024; +const MEM_POOL: usize = 4096; +const MEM_POOL_ENTRY_SIZE: usize = 2048; + +/// The ixgbe NIC device driver. +/// +/// `QS` is the ixgbe queue size, `QN` is the ixgbe queue num. +pub struct IxgbeNic { + inner: IxgbeDevice, + mem_pool: Arc, + rx_buffer_queue: VecDeque, +} + +unsafe impl Sync for IxgbeNic {} +unsafe impl Send for IxgbeNic {} + +impl IxgbeNic { + /// Creates a net ixgbe NIC instance and initialize, or returns a error if + /// any step fails. + pub fn init(base: usize, len: usize) -> DevResult { + let mem_pool = MemPool::allocate::(MEM_POOL, MEM_POOL_ENTRY_SIZE) + .map_err(|_| DevError::NoMemory)?; + let inner = IxgbeDevice::::init(base, len, QN, QN, &mem_pool).map_err(|err| { + log::error!("Failed to initialize ixgbe device: {:?}", err); + DevError::BadState + })?; + + let rx_buffer_queue = VecDeque::with_capacity(RX_BUFFER_SIZE); + Ok(Self { + inner, + mem_pool, + rx_buffer_queue, + }) + } +} + +impl BaseDriverOps for IxgbeNic { + fn device_name(&self) -> &str { + self.inner.get_driver_name() + } + + fn device_type(&self) -> DeviceType { + DeviceType::Net + } +} + +impl NetDriverOps for IxgbeNic { + fn mac_address(&self) -> EthernetAddress { + EthernetAddress(self.inner.get_mac_addr()) + } + + fn rx_queue_size(&self) -> usize { + QS + } + + fn tx_queue_size(&self) -> usize { + QS + } + + fn can_receive(&self) -> bool { + !self.rx_buffer_queue.is_empty() || self.inner.can_receive(0).unwrap() + } + + fn can_transmit(&self) -> bool { + // Default implementation is return true forever. + self.inner.can_send(0).unwrap() + } + + fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { + let rx_buf = ixgbe_ptr_to_buf(rx_buf, &self.mem_pool)?; + drop(rx_buf); + Ok(()) + } + + fn recycle_tx_buffers(&mut self) -> DevResult { + self.inner + .recycle_tx_buffers(0) + .map_err(|_| DevError::BadState)?; + Ok(()) + } + + fn receive(&mut self) -> DevResult { + if !self.can_receive() { + return Err(DevError::Again); + } + if !self.rx_buffer_queue.is_empty() { + // RX buffer have received packets. + Ok(self.rx_buffer_queue.pop_front().unwrap()) + } else { + let f = |rx_buf| { + let rx_buf = NetBufPtr::from(rx_buf); + self.rx_buffer_queue.push_back(rx_buf); + }; + + // RX queue is empty, receive from ixgbe NIC. + match self.inner.receive_packets(0, RECV_BATCH_SIZE, f) { + Ok(recv_nums) => { + if recv_nums == 0 { + // No packet is received, it is impossible things. + panic!("Error: No receive packets.") + } else { + Ok(self.rx_buffer_queue.pop_front().unwrap()) + } + } + Err(e) => match e { + IxgbeError::NotReady => Err(DevError::Again), + _ => Err(DevError::BadState), + }, + } + } + } + + fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { + let tx_buf = ixgbe_ptr_to_buf(tx_buf, &self.mem_pool)?; + match self.inner.send(0, tx_buf) { + Ok(_) => Ok(()), + Err(err) => match err { + IxgbeError::QueueFull => Err(DevError::Again), + _ => panic!("Unexpected err: {:?}", err), + }, + } + } + + fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { + let tx_buf = IxgbeNetBuf::alloc(&self.mem_pool, size).map_err(|_| DevError::NoMemory)?; + Ok(NetBufPtr::from(tx_buf)) + } +} + +impl From for NetBufPtr { + fn from(buf: IxgbeNetBuf) -> Self { + // Use `ManuallyDrop` to avoid drop `tx_buf`. + let mut buf = ManuallyDrop::new(buf); + // In ixgbe, `raw_ptr` is the pool entry, `buf_ptr` is the packet ptr, `len` is packet len + // to avoid too many dynamic memory allocation. + let buf_ptr = buf.packet_mut().as_mut_ptr(); + Self::new( + NonNull::new(buf.pool_entry() as *mut u8).unwrap(), + NonNull::new(buf_ptr).unwrap(), + buf.packet_len(), + ) + } +} + +// Converts a `NetBufPtr` to `IxgbeNetBuf`. +fn ixgbe_ptr_to_buf(ptr: NetBufPtr, pool: &Arc) -> DevResult { + IxgbeNetBuf::construct(ptr.raw_ptr.as_ptr() as usize, pool, ptr.len) + .map_err(|_| DevError::BadState) +} diff --git a/axdriver_net/src/lib.rs b/axdriver_net/src/lib.rs new file mode 100644 index 0000000..f0c5935 --- /dev/null +++ b/axdriver_net/src/lib.rs @@ -0,0 +1,106 @@ +//! Common traits and types for network device (NIC) drivers. + +#![no_std] +#![feature(const_mut_refs)] +#![feature(const_slice_from_raw_parts_mut)] + +#[cfg(feature = "ixgbe")] +/// ixgbe NIC device driver. +pub mod ixgbe; +mod net_buf; + +use core::ptr::NonNull; + +#[doc(no_inline)] +pub use axdriver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; + +pub use self::net_buf::{NetBuf, NetBufBox, NetBufPool}; + +/// The ethernet address of the NIC (MAC address). +pub struct EthernetAddress(pub [u8; 6]); + +/// Operations that require a network device (NIC) driver to implement. +pub trait NetDriverOps: BaseDriverOps { + /// The ethernet address of the NIC. + fn mac_address(&self) -> EthernetAddress; + + /// Whether can transmit packets. + fn can_transmit(&self) -> bool; + + /// Whether can receive packets. + fn can_receive(&self) -> bool; + + /// Size of the receive queue. + fn rx_queue_size(&self) -> usize; + + /// Size of the transmit queue. + fn tx_queue_size(&self) -> usize; + + /// Gives back the `rx_buf` to the receive queue for later receiving. + /// + /// `rx_buf` should be the same as the one returned by + /// [`NetDriverOps::receive`]. + fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult; + + /// Poll the transmit queue and gives back the buffers for previous transmiting. + /// returns [`DevResult`]. + fn recycle_tx_buffers(&mut self) -> DevResult; + + /// Transmits a packet in the buffer to the network, without blocking, + /// returns [`DevResult`]. + fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult; + + /// Receives a packet from the network and store it in the [`NetBuf`], + /// returns the buffer. + /// + /// Before receiving, the driver should have already populated some buffers + /// in the receive queue by [`NetDriverOps::recycle_rx_buffer`]. + /// + /// If currently no incomming packets, returns an error with type + /// [`DevError::Again`]. + fn receive(&mut self) -> DevResult; + + /// Allocate a memory buffer of a specified size for network transmission, + /// returns [`DevResult`] + fn alloc_tx_buffer(&mut self, size: usize) -> DevResult; +} + +/// A raw buffer struct for network device. +pub struct NetBufPtr { + // The raw pointer of the original object. + raw_ptr: NonNull, + // The pointer to the net buffer. + buf_ptr: NonNull, + len: usize, +} + +impl NetBufPtr { + /// Create a new [`NetBufPtr`]. + pub fn new(raw_ptr: NonNull, buf_ptr: NonNull, len: usize) -> Self { + Self { + raw_ptr, + buf_ptr, + len, + } + } + + /// Return raw pointer of the original object. + pub fn raw_ptr(&self) -> *mut T { + self.raw_ptr.as_ptr() as *mut T + } + + /// Return [`NetBufPtr`] buffer len. + pub fn packet_len(&self) -> usize { + self.len + } + + /// Return [`NetBufPtr`] buffer as &[u8]. + pub fn packet(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.buf_ptr.as_ptr() as *const u8, self.len) } + } + + /// Return [`NetBufPtr`] buffer as &mut [u8]. + pub fn packet_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.buf_ptr.as_ptr(), self.len) } + } +} diff --git a/axdriver_net/src/net_buf.rs b/axdriver_net/src/net_buf.rs new file mode 100644 index 0000000..6576749 --- /dev/null +++ b/axdriver_net/src/net_buf.rs @@ -0,0 +1,208 @@ +extern crate alloc; + +use crate::{DevError, DevResult, NetBufPtr}; +use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; +use core::ptr::NonNull; +use spin::Mutex; + +const MIN_BUFFER_LEN: usize = 1526; +const MAX_BUFFER_LEN: usize = 65535; + +/// A RAII network buffer wrapped in a [`Box`]. +pub type NetBufBox = Box; + +/// A RAII network buffer. +/// +/// It should be allocated from the [`NetBufPool`], and it will be +/// deallocated into the pool automatically when dropped. +/// +/// The layout of the buffer is: +/// +/// ```text +/// ______________________ capacity ______________________ +/// / \ +/// +------------------+------------------+------------------+ +/// | Header | Packet | Unused | +/// +------------------+------------------+------------------+ +/// |\__ header_len __/ \__ packet_len __/ +/// | +/// buf_ptr +/// ``` +pub struct NetBuf { + header_len: usize, + packet_len: usize, + capacity: usize, + buf_ptr: NonNull, + pool_offset: usize, + pool: Arc, +} + +unsafe impl Send for NetBuf {} +unsafe impl Sync for NetBuf {} + +impl NetBuf { + const unsafe fn get_slice(&self, start: usize, len: usize) -> &[u8] { + core::slice::from_raw_parts(self.buf_ptr.as_ptr().add(start), len) + } + + const unsafe fn get_slice_mut(&mut self, start: usize, len: usize) -> &mut [u8] { + core::slice::from_raw_parts_mut(self.buf_ptr.as_ptr().add(start), len) + } + + /// Returns the capacity of the buffer. + pub const fn capacity(&self) -> usize { + self.capacity + } + + /// Returns the length of the header part. + pub const fn header_len(&self) -> usize { + self.header_len + } + + /// Returns the header part of the buffer. + pub const fn header(&self) -> &[u8] { + unsafe { self.get_slice(0, self.header_len) } + } + + /// Returns the packet part of the buffer. + pub const fn packet(&self) -> &[u8] { + unsafe { self.get_slice(self.header_len, self.packet_len) } + } + + /// Returns the mutable reference to the packet part. + pub const fn packet_mut(&mut self) -> &mut [u8] { + unsafe { self.get_slice_mut(self.header_len, self.packet_len) } + } + + /// Returns both the header and the packet parts, as a contiguous slice. + pub const fn packet_with_header(&self) -> &[u8] { + unsafe { self.get_slice(0, self.header_len + self.packet_len) } + } + + /// Returns the entire buffer. + pub const fn raw_buf(&self) -> &[u8] { + unsafe { self.get_slice(0, self.capacity) } + } + + /// Returns the mutable reference to the entire buffer. + pub const fn raw_buf_mut(&mut self) -> &mut [u8] { + unsafe { self.get_slice_mut(0, self.capacity) } + } + + /// Set the length of the header part. + pub fn set_header_len(&mut self, header_len: usize) { + debug_assert!(header_len + self.packet_len <= self.capacity); + self.header_len = header_len; + } + + /// Set the length of the packet part. + pub fn set_packet_len(&mut self, packet_len: usize) { + debug_assert!(self.header_len + packet_len <= self.capacity); + self.packet_len = packet_len; + } + + /// Converts the buffer into a [`NetBufPtr`]. + pub fn into_buf_ptr(mut self: Box) -> NetBufPtr { + let buf_ptr = self.packet_mut().as_mut_ptr(); + let len = self.packet_len; + NetBufPtr::new( + NonNull::new(Box::into_raw(self) as *mut u8).unwrap(), + NonNull::new(buf_ptr).unwrap(), + len, + ) + } + + /// Restore [`NetBuf`] struct from a raw pointer. + /// + /// # Safety + /// + /// This function is unsafe because it may cause some memory issues, + /// so we must ensure that it is called after calling `into_buf_ptr`. + pub unsafe fn from_buf_ptr(ptr: NetBufPtr) -> Box { + Box::from_raw(ptr.raw_ptr::()) + } +} + +impl Drop for NetBuf { + /// Deallocates the buffer into the [`NetBufPool`]. + fn drop(&mut self) { + self.pool.dealloc(self.pool_offset); + } +} + +/// A pool of [`NetBuf`]s to speed up buffer allocation. +/// +/// It divides a large memory into several equal parts for each buffer. +pub struct NetBufPool { + capacity: usize, + buf_len: usize, + pool: Vec, + free_list: Mutex>, +} + +impl NetBufPool { + /// Creates a new pool with the given `capacity`, and all buffer lengths are + /// set to `buf_len`. + pub fn new(capacity: usize, buf_len: usize) -> DevResult> { + if capacity == 0 { + return Err(DevError::InvalidParam); + } + if !(MIN_BUFFER_LEN..=MAX_BUFFER_LEN).contains(&buf_len) { + return Err(DevError::InvalidParam); + } + + let pool = vec![0; capacity * buf_len]; + let mut free_list = Vec::with_capacity(capacity); + for i in 0..capacity { + free_list.push(i * buf_len); + } + Ok(Arc::new(Self { + capacity, + buf_len, + pool, + free_list: Mutex::new(free_list), + })) + } + + /// Returns the capacity of the pool. + pub const fn capacity(&self) -> usize { + self.capacity + } + + /// Returns the length of each buffer. + pub const fn buffer_len(&self) -> usize { + self.buf_len + } + + /// Allocates a buffer from the pool. + /// + /// Returns `None` if no buffer is available. + pub fn alloc(self: &Arc) -> Option { + let pool_offset = self.free_list.lock().pop()?; + let buf_ptr = + unsafe { NonNull::new(self.pool.as_ptr().add(pool_offset) as *mut u8).unwrap() }; + Some(NetBuf { + header_len: 0, + packet_len: 0, + capacity: self.buf_len, + buf_ptr, + pool_offset, + pool: Arc::clone(self), + }) + } + + /// Allocates a buffer wrapped in a [`Box`] from the pool. + /// + /// Returns `None` if no buffer is available. + pub fn alloc_boxed(self: &Arc) -> Option { + Some(Box::new(self.alloc()?)) + } + + /// Deallocates a buffer at the given offset. + /// + /// `pool_offset` must be a multiple of `buf_len`. + fn dealloc(&self, pool_offset: usize) { + debug_assert_eq!(pool_offset % self.buf_len, 0); + self.free_list.lock().push(pool_offset); + } +} diff --git a/axdriver_pci/Cargo.toml b/axdriver_pci/Cargo.toml new file mode 100644 index 0000000..3ab3ca7 --- /dev/null +++ b/axdriver_pci/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "axdriver_pci" +edition = "2021" +description = "Structures and functions for PCI bus operations" +documentation = "https://arceos-org.github.io/axdriver_crates/axdriver_pci" +keywords = ["arceos", "dirver", "pci"] +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true + +[dependencies] +virtio-drivers = "0.7.4" diff --git a/axdriver_pci/src/lib.rs b/axdriver_pci/src/lib.rs new file mode 100644 index 0000000..4ae0b9b --- /dev/null +++ b/axdriver_pci/src/lib.rs @@ -0,0 +1,53 @@ +//! Structures and functions for PCI bus operations. +//! +//! Currently, it just re-exports structures from the crate [virtio-drivers][1] +//! and its module [`virtio_drivers::transport::pci::bus`][2]. +//! +//! [1]: https://docs.rs/virtio-drivers/latest/virtio_drivers/ +//! [2]: https://docs.rs/virtio-drivers/latest/virtio_drivers/transport/pci/bus/index.html + +#![no_std] + +pub use virtio_drivers::transport::pci::bus::{BarInfo, Cam, HeaderType, MemoryBarType, PciError}; +pub use virtio_drivers::transport::pci::bus::{ + CapabilityInfo, Command, DeviceFunction, DeviceFunctionInfo, PciRoot, Status, +}; + +/// Used to allocate MMIO regions for PCI BARs. +pub struct PciRangeAllocator { + _start: u64, + end: u64, + current: u64, +} + +impl PciRangeAllocator { + /// Creates a new allocator from a memory range. + pub const fn new(base: u64, size: u64) -> Self { + Self { + _start: base, + end: base + size, + current: base, + } + } + + /// Allocates a memory region with the given size. + /// + /// The `size` should be a power of 2, and the returned value is also a + /// multiple of `size`. + pub fn alloc(&mut self, size: u64) -> Option { + if !size.is_power_of_two() { + return None; + } + let ret = align_up(self.current, size); + if ret + size > self.end { + return None; + } + + self.current = ret + size; + Some(ret) + } +} + +const fn align_up(addr: u64, align: u64) -> u64 { + (addr + align - 1) & !(align - 1) +} diff --git a/axdriver_virtio/Cargo.toml b/axdriver_virtio/Cargo.toml new file mode 100644 index 0000000..b668c62 --- /dev/null +++ b/axdriver_virtio/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "axdriver_virtio" +edition = "2021" +description = "Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `axdriver_base` series crates" +documentation = "https://arceos-org.github.io/axdriver_crates/axdriver_virtio" +keywords = ["arceos", "dirver", "vritio"] +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true + +[features] +block = ["axdriver_block"] +net = ["axdriver_net"] +gpu = ["axdriver_display"] + +[dependencies] +axdriver_base = { workspace = true } +axdriver_block = { workspace = true, optional = true } +axdriver_net = { workspace = true, optional = true } +axdriver_display = { workspace = true, optional = true} +virtio-drivers = "0.7.4" diff --git a/axdriver_virtio/src/blk.rs b/axdriver_virtio/src/blk.rs new file mode 100644 index 0000000..cdc0267 --- /dev/null +++ b/axdriver_virtio/src/blk.rs @@ -0,0 +1,60 @@ +use crate::as_dev_err; +use axdriver_base::{BaseDriverOps, DevResult, DeviceType}; +use axdriver_block::BlockDriverOps; +use virtio_drivers::{device::blk::VirtIOBlk as InnerDev, transport::Transport, Hal}; + +/// The VirtIO block device driver. +pub struct VirtIoBlkDev { + inner: InnerDev, +} + +unsafe impl Send for VirtIoBlkDev {} +unsafe impl Sync for VirtIoBlkDev {} + +impl VirtIoBlkDev { + /// Creates a new driver instance and initializes the device, or returns + /// an error if any step fails. + pub fn try_new(transport: T) -> DevResult { + Ok(Self { + inner: InnerDev::new(transport).map_err(as_dev_err)?, + }) + } +} + +impl BaseDriverOps for VirtIoBlkDev { + fn device_name(&self) -> &str { + "virtio-blk" + } + + fn device_type(&self) -> DeviceType { + DeviceType::Block + } +} + +impl BlockDriverOps for VirtIoBlkDev { + #[inline] + fn num_blocks(&self) -> u64 { + self.inner.capacity() + } + + #[inline] + fn block_size(&self) -> usize { + virtio_drivers::device::blk::SECTOR_SIZE + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { + self.inner + .read_blocks(block_id as _, buf) + .map_err(as_dev_err) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { + self.inner + .write_blocks(block_id as _, buf) + .map_err(as_dev_err) + } + + fn flush(&mut self) -> DevResult { + Ok(()) + } +} diff --git a/axdriver_virtio/src/gpu.rs b/axdriver_virtio/src/gpu.rs new file mode 100644 index 0000000..cf4e89a --- /dev/null +++ b/axdriver_virtio/src/gpu.rs @@ -0,0 +1,70 @@ +extern crate alloc; +use crate::as_dev_err; + +use axdriver_base::{BaseDriverOps, DevResult, DeviceType}; +use axdriver_display::{DisplayDriverOps, DisplayInfo, FrameBuffer}; +use virtio_drivers::{device::gpu::VirtIOGpu as InnerDev, transport::Transport, Hal}; + +/// The VirtIO GPU device driver. +pub struct VirtIoGpuDev { + inner: InnerDev, + info: DisplayInfo, +} + +unsafe impl Send for VirtIoGpuDev {} +unsafe impl Sync for VirtIoGpuDev {} + +impl VirtIoGpuDev { + /// Creates a new driver instance and initializes the device, or returns + /// an error if any step fails. + pub fn try_new(transport: T) -> DevResult { + let mut virtio = InnerDev::new(transport).unwrap(); + + // get framebuffer + let fbuffer = virtio.setup_framebuffer().unwrap(); + let fb_base_vaddr = fbuffer.as_mut_ptr() as usize; + let fb_size = fbuffer.len(); + let (width, height) = virtio.resolution().unwrap(); + let info = DisplayInfo { + width, + height, + fb_base_vaddr, + fb_size, + }; + + Ok(Self { + inner: virtio, + info, + }) + } +} + +impl BaseDriverOps for VirtIoGpuDev { + fn device_name(&self) -> &str { + "virtio-gpu" + } + + fn device_type(&self) -> DeviceType { + DeviceType::Display + } +} + +impl DisplayDriverOps for VirtIoGpuDev { + fn info(&self) -> DisplayInfo { + self.info + } + + fn fb(&self) -> FrameBuffer { + unsafe { + FrameBuffer::from_raw_parts_mut(self.info.fb_base_vaddr as *mut u8, self.info.fb_size) + } + } + + fn need_flush(&self) -> bool { + true + } + + fn flush(&mut self) -> DevResult { + self.inner.flush().map_err(as_dev_err) + } +} diff --git a/axdriver_virtio/src/lib.rs b/axdriver_virtio/src/lib.rs new file mode 100644 index 0000000..89e447c --- /dev/null +++ b/axdriver_virtio/src/lib.rs @@ -0,0 +1,97 @@ +//! Wrappers of some devices in the [`virtio-drivers`][1] crate, that implement +//! traits in the [`axdriver_base`][2] series crates. +//! +//! Like the [`virtio-drivers`][1] crate, you must implement the [`VirtIoHal`] +//! trait (alias of [`virtio-drivers::Hal`][3]), to allocate DMA regions and +//! translate between physical addresses (as seen by devices) and virtual +//! addresses (as seen by your program). +//! +//! [1]: https://docs.rs/virtio-drivers/latest/virtio_drivers/ +//! [2]: https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_base +//! [3]: https://docs.rs/virtio-drivers/latest/virtio_drivers/trait.Hal.html + +#![no_std] +#![feature(doc_auto_cfg)] + +#[cfg(feature = "block")] +mod blk; +#[cfg(feature = "gpu")] +mod gpu; +#[cfg(feature = "net")] +mod net; + +#[cfg(feature = "block")] +pub use self::blk::VirtIoBlkDev; +#[cfg(feature = "gpu")] +pub use self::gpu::VirtIoGpuDev; +#[cfg(feature = "net")] +pub use self::net::VirtIoNetDev; + +pub use virtio_drivers::transport::pci::bus as pci; +pub use virtio_drivers::transport::{mmio::MmioTransport, pci::PciTransport, Transport}; +pub use virtio_drivers::{BufferDirection, Hal as VirtIoHal, PhysAddr}; + +use self::pci::{DeviceFunction, DeviceFunctionInfo, PciRoot}; +use axdriver_base::{DevError, DeviceType}; +use virtio_drivers::transport::DeviceType as VirtIoDevType; + +/// Try to probe a VirtIO MMIO device from the given memory region. +/// +/// If the device is recognized, returns the device type and a transport object +/// for later operations. Otherwise, returns [`None`]. +pub fn probe_mmio_device( + reg_base: *mut u8, + _reg_size: usize, +) -> Option<(DeviceType, MmioTransport)> { + use core::ptr::NonNull; + use virtio_drivers::transport::mmio::VirtIOHeader; + + let header = NonNull::new(reg_base as *mut VirtIOHeader).unwrap(); + let transport = unsafe { MmioTransport::new(header) }.ok()?; + let dev_type = as_dev_type(transport.device_type())?; + Some((dev_type, transport)) +} + +/// Try to probe a VirtIO PCI device from the given PCI address. +/// +/// If the device is recognized, returns the device type and a transport object +/// for later operations. Otherwise, returns [`None`]. +pub fn probe_pci_device( + root: &mut PciRoot, + bdf: DeviceFunction, + dev_info: &DeviceFunctionInfo, +) -> Option<(DeviceType, PciTransport)> { + use virtio_drivers::transport::pci::virtio_device_type; + + let dev_type = virtio_device_type(dev_info).and_then(as_dev_type)?; + let transport = PciTransport::new::(root, bdf).ok()?; + Some((dev_type, transport)) +} + +const fn as_dev_type(t: VirtIoDevType) -> Option { + use VirtIoDevType::*; + match t { + Block => Some(DeviceType::Block), + Network => Some(DeviceType::Net), + GPU => Some(DeviceType::Display), + _ => None, + } +} + +#[allow(dead_code)] +const fn as_dev_err(e: virtio_drivers::Error) -> DevError { + use virtio_drivers::Error::*; + match e { + QueueFull => DevError::BadState, + NotReady => DevError::Again, + WrongToken => DevError::BadState, + AlreadyUsed => DevError::AlreadyExists, + InvalidParam => DevError::InvalidParam, + DmaError => DevError::NoMemory, + IoError => DevError::Io, + Unsupported => DevError::Unsupported, + ConfigSpaceTooSmall => DevError::BadState, + ConfigSpaceMissing => DevError::BadState, + _ => DevError::BadState, + } +} diff --git a/axdriver_virtio/src/net.rs b/axdriver_virtio/src/net.rs new file mode 100644 index 0000000..b34b686 --- /dev/null +++ b/axdriver_virtio/src/net.rs @@ -0,0 +1,193 @@ +use crate::as_dev_err; +use alloc::{sync::Arc, vec::Vec}; +use axdriver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; +use axdriver_net::{EthernetAddress, NetBuf, NetBufBox, NetBufPool, NetBufPtr, NetDriverOps}; +use virtio_drivers::{device::net::VirtIONetRaw as InnerDev, transport::Transport, Hal}; + +extern crate alloc; + +const NET_BUF_LEN: usize = 1526; + +/// The VirtIO network device driver. +/// +/// `QS` is the VirtIO queue size. +pub struct VirtIoNetDev { + rx_buffers: [Option; QS], + tx_buffers: [Option; QS], + free_tx_bufs: Vec, + buf_pool: Arc, + inner: InnerDev, +} + +unsafe impl Send for VirtIoNetDev {} +unsafe impl Sync for VirtIoNetDev {} + +impl VirtIoNetDev { + /// Creates a new driver instance and initializes the device, or returns + /// an error if any step fails. + pub fn try_new(transport: T) -> DevResult { + // 0. Create a new driver instance. + const NONE_BUF: Option = None; + let inner = InnerDev::new(transport).map_err(as_dev_err)?; + let rx_buffers = [NONE_BUF; QS]; + let tx_buffers = [NONE_BUF; QS]; + let buf_pool = NetBufPool::new(2 * QS, NET_BUF_LEN)?; + let free_tx_bufs = Vec::with_capacity(QS); + + let mut dev = Self { + rx_buffers, + inner, + tx_buffers, + free_tx_bufs, + buf_pool, + }; + + // 1. Fill all rx buffers. + for (i, rx_buf_place) in dev.rx_buffers.iter_mut().enumerate() { + let mut rx_buf = dev.buf_pool.alloc_boxed().ok_or(DevError::NoMemory)?; + // Safe because the buffer lives as long as the queue. + let token = unsafe { + dev.inner + .receive_begin(rx_buf.raw_buf_mut()) + .map_err(as_dev_err)? + }; + assert_eq!(token, i as u16); + *rx_buf_place = Some(rx_buf); + } + + // 2. Allocate all tx buffers. + for _ in 0..QS { + let mut tx_buf = dev.buf_pool.alloc_boxed().ok_or(DevError::NoMemory)?; + // Fill header + let hdr_len = dev + .inner + .fill_buffer_header(tx_buf.raw_buf_mut()) + .or(Err(DevError::InvalidParam))?; + tx_buf.set_header_len(hdr_len); + dev.free_tx_bufs.push(tx_buf); + } + + // 3. Return the driver instance. + Ok(dev) + } +} + +impl BaseDriverOps for VirtIoNetDev { + fn device_name(&self) -> &str { + "virtio-net" + } + + fn device_type(&self) -> DeviceType { + DeviceType::Net + } +} + +impl NetDriverOps for VirtIoNetDev { + #[inline] + fn mac_address(&self) -> EthernetAddress { + EthernetAddress(self.inner.mac_address()) + } + + #[inline] + fn can_transmit(&self) -> bool { + !self.free_tx_bufs.is_empty() && self.inner.can_send() + } + + #[inline] + fn can_receive(&self) -> bool { + self.inner.poll_receive().is_some() + } + + #[inline] + fn rx_queue_size(&self) -> usize { + QS + } + + #[inline] + fn tx_queue_size(&self) -> usize { + QS + } + + fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { + let mut rx_buf = unsafe { NetBuf::from_buf_ptr(rx_buf) }; + // Safe because we take the ownership of `rx_buf` back to `rx_buffers`, + // it lives as long as the queue. + let new_token = unsafe { + self.inner + .receive_begin(rx_buf.raw_buf_mut()) + .map_err(as_dev_err)? + }; + // `rx_buffers[new_token]` is expected to be `None` since it was taken + // away at `Self::receive()` and has not been added back. + if self.rx_buffers[new_token as usize].is_some() { + return Err(DevError::BadState); + } + self.rx_buffers[new_token as usize] = Some(rx_buf); + Ok(()) + } + + fn recycle_tx_buffers(&mut self) -> DevResult { + while let Some(token) = self.inner.poll_transmit() { + let tx_buf = self.tx_buffers[token as usize] + .take() + .ok_or(DevError::BadState)?; + unsafe { + self.inner + .transmit_complete(token, tx_buf.packet_with_header()) + .map_err(as_dev_err)?; + } + // Recycle the buffer. + self.free_tx_bufs.push(tx_buf); + } + Ok(()) + } + + fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { + // 0. prepare tx buffer. + let tx_buf = unsafe { NetBuf::from_buf_ptr(tx_buf) }; + // 1. transmit packet. + let token = unsafe { + self.inner + .transmit_begin(tx_buf.packet_with_header()) + .map_err(as_dev_err)? + }; + self.tx_buffers[token as usize] = Some(tx_buf); + Ok(()) + } + + fn receive(&mut self) -> DevResult { + if let Some(token) = self.inner.poll_receive() { + let mut rx_buf = self.rx_buffers[token as usize] + .take() + .ok_or(DevError::BadState)?; + // Safe because the buffer lives as long as the queue. + let (hdr_len, pkt_len) = unsafe { + self.inner + .receive_complete(token, rx_buf.raw_buf_mut()) + .map_err(as_dev_err)? + }; + rx_buf.set_header_len(hdr_len); + rx_buf.set_packet_len(pkt_len); + + Ok(rx_buf.into_buf_ptr()) + } else { + Err(DevError::Again) + } + } + + fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { + // 0. Allocate a buffer from the queue. + let mut net_buf = self.free_tx_bufs.pop().ok_or(DevError::NoMemory)?; + let pkt_len = size; + + // 1. Check if the buffer is large enough. + let hdr_len = net_buf.header_len(); + if hdr_len + pkt_len > net_buf.capacity() { + return Err(DevError::InvalidParam); + } + net_buf.set_packet_len(pkt_len); + + // 2. Return the buffer. + Ok(net_buf.into_buf_ptr()) + } +}