mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-25 02:13:24 +00:00
Add Ext2 fs and basic bio layer
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
1616f2d32c
commit
9473889c6b
@ -8,10 +8,13 @@ edition = "2021"
|
||||
[dependencies]
|
||||
bitflags = "1.3"
|
||||
spin = "0.9.4"
|
||||
pod = { git = "https://github.com/asterinas/pod", rev = "d7dba56" }
|
||||
aster-frame = { path = "../../../framework/aster-frame" }
|
||||
aster-util = { path = "../../libs/aster-util" }
|
||||
int-to-c-enum = { path = "../../libs/int-to-c-enum" }
|
||||
component = { path = "../../libs/comp-sys/component" }
|
||||
log = "0.4"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
[features]
|
||||
|
||||
|
475
services/comps/block/src/bio.rs
Normal file
475
services/comps/block/src/bio.rs
Normal file
@ -0,0 +1,475 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::{id::Sid, BlockDevice};
|
||||
|
||||
use aster_frame::{
|
||||
sync::WaitQueue,
|
||||
vm::{VmFrame, VmReader, VmSegment, VmWriter},
|
||||
};
|
||||
use int_to_c_enum::TryFromInt;
|
||||
|
||||
/// The unit for block I/O.
|
||||
///
|
||||
/// Each `Bio` packs the following information:
|
||||
/// (1) The type of the I/O,
|
||||
/// (2) The target sectors on the device for doing I/O,
|
||||
/// (3) The memory locations (`BioSegment`) from/to which data are read/written,
|
||||
/// (4) The optional callback function that will be invoked when the I/O is completed.
|
||||
pub struct Bio(Arc<BioInner>);
|
||||
|
||||
impl Bio {
|
||||
/// Constructs a new `Bio`.
|
||||
///
|
||||
/// The `type_` describes the type of the I/O.
|
||||
/// The `start_sid` is the starting sector id on the device.
|
||||
/// The `segments` describes the memory segments.
|
||||
/// The `complete_fn` is the optional callback function.
|
||||
pub fn new(
|
||||
type_: BioType,
|
||||
start_sid: Sid,
|
||||
segments: Vec<BioSegment>,
|
||||
complete_fn: Option<fn(&SubmittedBio)>,
|
||||
) -> Self {
|
||||
let nsectors = segments
|
||||
.iter()
|
||||
.map(|segment| segment.nsectors().to_raw())
|
||||
.sum();
|
||||
|
||||
let inner = Arc::new(BioInner {
|
||||
type_,
|
||||
sid_range: start_sid..start_sid + nsectors,
|
||||
segments,
|
||||
complete_fn,
|
||||
status: AtomicU32::new(BioStatus::Init as u32),
|
||||
wait_queue: WaitQueue::new(),
|
||||
});
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
/// Returns the type.
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.0.type_()
|
||||
}
|
||||
|
||||
/// Returns the range of target sectors on the device.
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
self.0.sid_range()
|
||||
}
|
||||
|
||||
/// Returns the slice to the memory segments.
|
||||
pub fn segments(&self) -> &[BioSegment] {
|
||||
self.0.segments()
|
||||
}
|
||||
|
||||
/// Returns the status.
|
||||
pub fn status(&self) -> BioStatus {
|
||||
self.0.status()
|
||||
}
|
||||
|
||||
/// Submits self to the `block_device` asynchronously.
|
||||
///
|
||||
/// Returns a `BioWaiter` to the caller to wait for its completion.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// The caller must not submit a `Bio` more than once. Otherwise, a panic shall be triggered.
|
||||
pub fn submit(&self, block_device: &dyn BlockDevice) -> Result<BioWaiter, BioEnqueueError> {
|
||||
// Change the status from "Init" to "Submit".
|
||||
let result = self.0.status.compare_exchange(
|
||||
BioStatus::Init as u32,
|
||||
BioStatus::Submit as u32,
|
||||
Ordering::Release,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
||||
if let Err(e) = block_device
|
||||
.request_queue()
|
||||
.enqueue(SubmittedBio(self.0.clone()))
|
||||
{
|
||||
// Fail to submit, revert the status.
|
||||
let result = self.0.status.compare_exchange(
|
||||
BioStatus::Submit as u32,
|
||||
BioStatus::Init as u32,
|
||||
Ordering::Release,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(BioWaiter {
|
||||
bios: vec![self.0.clone()],
|
||||
})
|
||||
}
|
||||
|
||||
/// Submits self to the `block_device` and waits for the result synchronously.
|
||||
///
|
||||
/// Returns the result status of the `Bio`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// The caller must not submit a `Bio` more than once. Otherwise, a panic shall be triggered.
|
||||
pub fn submit_sync(
|
||||
&self,
|
||||
block_device: &dyn BlockDevice,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let waiter = self.submit(block_device)?;
|
||||
match waiter.wait() {
|
||||
Some(status) => {
|
||||
assert!(status == BioStatus::Complete);
|
||||
Ok(status)
|
||||
}
|
||||
None => {
|
||||
let status = self.status();
|
||||
assert!(status != BioStatus::Complete);
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type returned when enqueueing the `Bio`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum BioEnqueueError {
|
||||
/// The request queue is full
|
||||
IsFull,
|
||||
/// Refuse to enqueue the bio
|
||||
Refused,
|
||||
}
|
||||
|
||||
impl From<BioEnqueueError> for aster_frame::Error {
|
||||
fn from(_error: BioEnqueueError) -> Self {
|
||||
aster_frame::Error::NotEnoughResources
|
||||
}
|
||||
}
|
||||
|
||||
/// A waiter for `Bio` submissions.
|
||||
///
|
||||
/// This structure holds a list of `Bio` requests and provides functionality to
|
||||
/// wait for their completion and retrieve their statuses.
|
||||
#[must_use]
|
||||
pub struct BioWaiter {
|
||||
bios: Vec<Arc<BioInner>>,
|
||||
}
|
||||
|
||||
impl BioWaiter {
|
||||
/// Constructs a new `BioWaiter` instance with no `Bio` requests.
|
||||
pub fn new() -> Self {
|
||||
Self { bios: Vec::new() }
|
||||
}
|
||||
|
||||
/// Returns the number of `Bio` requests associated with `self`.
|
||||
pub fn nreqs(&self) -> usize {
|
||||
self.bios.len()
|
||||
}
|
||||
|
||||
/// Gets the `index`-th `Bio` request associated with `self`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `index` is out of bounds, this method will panic.
|
||||
pub fn req(&self, index: usize) -> Bio {
|
||||
Bio(self.bios[index].clone())
|
||||
}
|
||||
|
||||
/// Returns the status of the `index`-th `Bio` request associated with `self`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `index` is out of bounds, this method will panic.
|
||||
pub fn status(&self, index: usize) -> BioStatus {
|
||||
self.bios[index].status()
|
||||
}
|
||||
|
||||
/// Merges the `Bio` requests from another `BioWaiter` into this one.
|
||||
///
|
||||
/// The another `BioWaiter`'s `Bio` requests are appended to the end of
|
||||
/// the `Bio` list of `self`, effectively concatenating the two lists.
|
||||
pub fn concat(&mut self, mut other: Self) {
|
||||
self.bios.append(&mut other.bios);
|
||||
}
|
||||
|
||||
/// Waits for the completion of all `Bio` requests.
|
||||
///
|
||||
/// This method iterates through each `Bio` in the list, waiting for their
|
||||
/// completion.
|
||||
///
|
||||
/// The return value is an option indicating whether all the requests in the list
|
||||
/// have successfully completed.
|
||||
/// On success this value is guaranteed to be equal to `Some(BioStatus::Complete)`.
|
||||
pub fn wait(&self) -> Option<BioStatus> {
|
||||
let mut ret = Some(BioStatus::Complete);
|
||||
|
||||
for bio in self.bios.iter() {
|
||||
let status = bio.wait_queue.wait_until(|| {
|
||||
let status = bio.status();
|
||||
if status != BioStatus::Submit {
|
||||
Some(status)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if status != BioStatus::Complete && ret.is_some() {
|
||||
ret = None;
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BioWaiter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A submitted `Bio` object.
|
||||
///
|
||||
/// The request queue of block device only accepts a `SubmittedBio` into the queue.
|
||||
pub struct SubmittedBio(Arc<BioInner>);
|
||||
|
||||
impl SubmittedBio {
|
||||
/// Returns the type.
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.0.type_()
|
||||
}
|
||||
|
||||
/// Returns the range of target sectors on the device.
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
self.0.sid_range()
|
||||
}
|
||||
|
||||
/// Returns the slice to the memory segments.
|
||||
pub fn segments(&self) -> &[BioSegment] {
|
||||
self.0.segments()
|
||||
}
|
||||
|
||||
/// Returns the status.
|
||||
pub fn status(&self) -> BioStatus {
|
||||
self.0.status()
|
||||
}
|
||||
|
||||
/// Completes the `Bio` with the `status` and invokes the callback function.
|
||||
///
|
||||
/// When the driver finishes the request for this `Bio`, it will call this method.
|
||||
pub fn complete(&self, status: BioStatus) {
|
||||
assert!(status != BioStatus::Init && status != BioStatus::Submit);
|
||||
|
||||
// Set the status.
|
||||
let result = self.0.status.compare_exchange(
|
||||
BioStatus::Submit as u32,
|
||||
status as u32,
|
||||
Ordering::Release,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
||||
self.0.wait_queue.wake_all();
|
||||
if let Some(complete_fn) = self.0.complete_fn {
|
||||
complete_fn(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The common inner part of `Bio`.
|
||||
struct BioInner {
|
||||
/// The type of the I/O
|
||||
type_: BioType,
|
||||
/// The range of the sector id on device
|
||||
sid_range: Range<Sid>,
|
||||
/// The memory segments in this `Bio`
|
||||
segments: Vec<BioSegment>,
|
||||
/// The I/O completion method
|
||||
complete_fn: Option<fn(&SubmittedBio)>,
|
||||
/// The I/O status
|
||||
status: AtomicU32,
|
||||
/// The wait queue for I/O completion
|
||||
wait_queue: WaitQueue,
|
||||
}
|
||||
|
||||
impl BioInner {
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.type_
|
||||
}
|
||||
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
&self.sid_range
|
||||
}
|
||||
|
||||
pub fn segments(&self) -> &[BioSegment] {
|
||||
&self.segments
|
||||
}
|
||||
|
||||
pub fn status(&self) -> BioStatus {
|
||||
BioStatus::try_from(self.status.load(Ordering::Relaxed)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of `Bio`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, TryFromInt)]
|
||||
#[repr(u8)]
|
||||
pub enum BioType {
|
||||
/// Read sectors from the device.
|
||||
Read = 0,
|
||||
/// Write sectors into the device.
|
||||
Write = 1,
|
||||
/// Flush the volatile write cache.
|
||||
Flush = 2,
|
||||
/// Discard sectors.
|
||||
Discard = 3,
|
||||
}
|
||||
|
||||
/// The status of `Bio`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, TryFromInt)]
|
||||
#[repr(u32)]
|
||||
pub enum BioStatus {
|
||||
/// The initial status for a newly created `Bio`.
|
||||
Init = 0,
|
||||
/// After a `Bio` is submitted, its status will be changed to "Submit".
|
||||
Submit = 1,
|
||||
/// The I/O operation has been successfully completed.
|
||||
Complete = 2,
|
||||
/// The I/O operation is not supported.
|
||||
NotSupported = 3,
|
||||
/// Insufficient space is available to perform the I/O operation.
|
||||
NoSpace = 4,
|
||||
/// An error occurred while doing I/O.
|
||||
IoError = 5,
|
||||
}
|
||||
|
||||
/// `BioSegment` is a smallest memory unit in block I/O.
|
||||
///
|
||||
/// It is a contiguous memory region that contains multiple sectors.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BioSegment {
|
||||
/// The contiguous pages on which this segment resides.
|
||||
pages: Pages,
|
||||
/// The offset (in bytes) relative to the first page.
|
||||
offset: AlignedUsize<SECTOR_SIZE>,
|
||||
// The length (in bytes), may cross pages.
|
||||
len: AlignedUsize<SECTOR_SIZE>,
|
||||
}
|
||||
|
||||
const SECTOR_SIZE: u16 = super::SECTOR_SIZE as u16;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Pages {
|
||||
Frame(VmFrame),
|
||||
Segment(VmSegment),
|
||||
}
|
||||
|
||||
impl<'a> BioSegment {
|
||||
/// Constructs a new `BioSegment` from `VmSegment`.
|
||||
pub fn from_segment(segment: VmSegment, offset: usize, len: usize) -> Self {
|
||||
assert!(offset + len <= segment.nbytes());
|
||||
|
||||
Self {
|
||||
pages: Pages::Segment(segment),
|
||||
offset: AlignedUsize::<SECTOR_SIZE>::new(offset).unwrap(),
|
||||
len: AlignedUsize::<SECTOR_SIZE>::new(len).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new `BioSegment` from `VmFrame`.
|
||||
pub fn from_frame(frame: VmFrame, offset: usize, len: usize) -> Self {
|
||||
assert!(offset + len <= super::BLOCK_SIZE);
|
||||
|
||||
Self {
|
||||
pages: Pages::Frame(frame),
|
||||
offset: AlignedUsize::<SECTOR_SIZE>::new(offset).unwrap(),
|
||||
len: AlignedUsize::<SECTOR_SIZE>::new(len).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of sectors.
|
||||
pub fn nsectors(&self) -> Sid {
|
||||
Sid::from_offset(self.len.value())
|
||||
}
|
||||
|
||||
/// Returns the number of bytes.
|
||||
pub fn nbytes(&self) -> usize {
|
||||
self.len.value()
|
||||
}
|
||||
|
||||
/// Returns a reader to read data from it.
|
||||
pub fn reader(&'a self) -> VmReader<'a> {
|
||||
let reader = match &self.pages {
|
||||
Pages::Segment(segment) => segment.reader(),
|
||||
Pages::Frame(frame) => frame.reader(),
|
||||
};
|
||||
reader.skip(self.offset.value()).limit(self.len.value())
|
||||
}
|
||||
|
||||
/// Returns a writer to write data into it.
|
||||
pub fn writer(&'a self) -> VmWriter<'a> {
|
||||
let writer = match &self.pages {
|
||||
Pages::Segment(segment) => segment.writer(),
|
||||
Pages::Frame(frame) => frame.writer(),
|
||||
};
|
||||
writer.skip(self.offset.value()).limit(self.len.value())
|
||||
}
|
||||
}
|
||||
|
||||
/// An aligned unsigned integer number.
|
||||
///
|
||||
/// An instance of `AlignedUsize<const N: u16>` is guaranteed to have a value that is a multiple
|
||||
/// of `N`, a predetermined const value. It is preferable to express an unsigned integer value
|
||||
/// in type `AlignedUsize<_>` instead of `usize` if the value must satisfy an alignment requirement.
|
||||
/// This helps readability and prevents bugs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// const SECTOR_SIZE: u16 = 512;
|
||||
///
|
||||
/// let sector_num = 1234; // The 1234-th sector
|
||||
/// let sector_offset: AlignedUsize<SECTOR_SIZE> = {
|
||||
/// let sector_offset = sector_num * (SECTOR_SIZE as usize);
|
||||
/// AlignedUsize::<SECTOR_SIZE>::new(sector_offset).unwrap()
|
||||
/// };
|
||||
/// assert!(sector_offset.value() % sector_offset.align() == 0);
|
||||
/// ```
|
||||
///
|
||||
/// # Limitation
|
||||
///
|
||||
/// Currently, the alignment const value must be expressed in `u16`;
|
||||
/// it is not possible to use a larger or smaller type.
|
||||
/// This limitation is inherited from that of Rust's const generics:
|
||||
/// your code can be generic over the _value_ of a const, but not the _type_ of the const.
|
||||
/// We choose `u16` because it is reasonably large to represent any alignment value
|
||||
/// used in practice.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AlignedUsize<const N: u16>(usize);
|
||||
|
||||
impl<const N: u16> AlignedUsize<N> {
|
||||
/// Constructs a new instance of aligned integer if the given value is aligned.
|
||||
pub fn new(val: usize) -> Option<Self> {
|
||||
if val % (N as usize) == 0 {
|
||||
Some(Self(val))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value.
|
||||
pub fn value(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the corresponding ID.
|
||||
///
|
||||
/// The so-called "ID" of an aligned integer is defined to be `self.value() / self.align()`.
|
||||
/// This value is named ID because one common use case is using `Aligned` to express
|
||||
/// the byte offset of a sector, block, or page. In this case, the `id` method returns
|
||||
/// the ID of the corresponding sector, block, or page.
|
||||
pub fn id(&self) -> usize {
|
||||
self.value() / self.align()
|
||||
}
|
||||
|
||||
/// Returns the alignment.
|
||||
pub fn align(&self) -> usize {
|
||||
N as usize
|
||||
}
|
||||
}
|
100
services/comps/block/src/id.rs
Normal file
100
services/comps/block/src/id.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use core::{
|
||||
iter::Step,
|
||||
ops::{Add, Sub},
|
||||
};
|
||||
use pod::Pod;
|
||||
use static_assertions::const_assert;
|
||||
|
||||
/// The block index used in the filesystem.
|
||||
pub type Bid = BlockId<BLOCK_SIZE>;
|
||||
/// The sector index used in the device.
|
||||
pub type Sid = BlockId<SECTOR_SIZE>;
|
||||
|
||||
impl From<Bid> for Sid {
|
||||
fn from(bid: Bid) -> Self {
|
||||
Self::new(bid.to_raw() * (BLOCK_SIZE / SECTOR_SIZE) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
const BLOCK_SIZE: u16 = super::BLOCK_SIZE as u16;
|
||||
const SECTOR_SIZE: u16 = super::SECTOR_SIZE as u16;
|
||||
const_assert!(BLOCK_SIZE / SECTOR_SIZE >= 1);
|
||||
|
||||
/// An index of a block.
|
||||
///
|
||||
/// The `BlockId<const N: u16>` is a generic type that is parameterized by a constant `N`, which
|
||||
/// represents the size of each block in bytes. The `BlockId<_>` provides a type-safe way of handling
|
||||
/// block indices.
|
||||
/// An Instance of `BlockId<_>` is guaranteed to represent valid block index, derived from byte offset
|
||||
/// and the specified block size `N`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// const BLOCK_SIZE: u16 = 512;
|
||||
///
|
||||
/// let bytes_offset = 2048;
|
||||
/// let block_id = BlockId<BLOCK_SIZE>::from_offset(bytes_offset);
|
||||
/// assert!(block_id == (bytes_offset / BLOCK_SIZE));
|
||||
/// ```
|
||||
///
|
||||
/// # Limitation
|
||||
///
|
||||
/// Currently, the block size is expressed in `u16`. We choose `u16` because
|
||||
/// it is reasonably large to represent the common block size used in practice.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Pod)]
|
||||
pub struct BlockId<const N: u16>(u64);
|
||||
|
||||
impl<const N: u16> BlockId<N> {
|
||||
/// Constructs an id from a raw id.
|
||||
pub const fn new(raw_id: u64) -> Self {
|
||||
Self(raw_id)
|
||||
}
|
||||
|
||||
/// Constructs an id from a byte offset.
|
||||
pub const fn from_offset(offset: usize) -> Self {
|
||||
Self((offset / (N as usize)) as _)
|
||||
}
|
||||
|
||||
/// Converts to a byte offset.
|
||||
pub fn to_offset(self) -> usize {
|
||||
(self.0 as usize) * (N as usize)
|
||||
}
|
||||
|
||||
/// Converts to raw id.
|
||||
pub fn to_raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: u16> Add<u64> for BlockId<N> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: u64) -> Self::Output {
|
||||
Self(self.0 + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: u16> Sub<u64> for BlockId<N> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: u64) -> Self::Output {
|
||||
Self(self.0 - other)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `Step` trait to iterate over `Range<Id>`.
|
||||
impl<const N: u16> Step for BlockId<N> {
|
||||
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
|
||||
u64::steps_between(&start.0, &end.0)
|
||||
}
|
||||
|
||||
fn forward_checked(start: Self, count: usize) -> Option<Self> {
|
||||
u64::forward_checked(start.0, count).map(Self::new)
|
||||
}
|
||||
|
||||
fn backward_checked(start: Self, count: usize) -> Option<Self> {
|
||||
u64::backward_checked(start.0, count).map(Self::new)
|
||||
}
|
||||
}
|
237
services/comps/block/src/impl_block_device.rs
Normal file
237
services/comps/block/src/impl_block_device.rs
Normal file
@ -0,0 +1,237 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::{
|
||||
bio::{Bio, BioEnqueueError, BioSegment, BioStatus, BioType, BioWaiter, SubmittedBio},
|
||||
id::{Bid, Sid},
|
||||
BlockDevice, BLOCK_SIZE, SECTOR_SIZE,
|
||||
};
|
||||
|
||||
use aster_frame::vm::{VmAllocOptions, VmFrame, VmIo, VmSegment};
|
||||
|
||||
/// Implements several commonly used APIs for the block device to conveniently
|
||||
/// read and write block(s).
|
||||
impl dyn BlockDevice {
|
||||
/// Synchronously reads contiguous blocks starting from the `bid`.
|
||||
pub fn read_blocks_sync(
|
||||
&self,
|
||||
bid: Bid,
|
||||
segment: &VmSegment,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Read, bid, segment);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously reads contiguous blocks starting from the `bid`.
|
||||
pub fn read_blocks(&self, bid: Bid, segment: &VmSegment) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Read, bid, segment);
|
||||
bio.submit(self)
|
||||
}
|
||||
|
||||
/// Synchronously reads one block indicated by the `bid`.
|
||||
pub fn read_block_sync(&self, bid: Bid, frame: &VmFrame) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Read, bid, frame);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously reads one block indicated by the `bid`.
|
||||
pub fn read_block(&self, bid: Bid, frame: &VmFrame) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Read, bid, frame);
|
||||
bio.submit(self)
|
||||
}
|
||||
|
||||
/// Synchronously writes contiguous blocks starting from the `bid`.
|
||||
pub fn write_blocks_sync(
|
||||
&self,
|
||||
bid: Bid,
|
||||
segment: &VmSegment,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Write, bid, segment);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously writes contiguous blocks starting from the `bid`.
|
||||
pub fn write_blocks(
|
||||
&self,
|
||||
bid: Bid,
|
||||
segment: &VmSegment,
|
||||
) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Write, bid, segment);
|
||||
bio.submit(self)
|
||||
}
|
||||
|
||||
/// Synchronously writes one block indicated by the `bid`.
|
||||
pub fn write_block_sync(
|
||||
&self,
|
||||
bid: Bid,
|
||||
frame: &VmFrame,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Write, bid, frame);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously writes one block indicated by the `bid`.
|
||||
pub fn write_block(&self, bid: Bid, frame: &VmFrame) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Write, bid, frame);
|
||||
bio.submit(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl VmIo for dyn BlockDevice {
|
||||
/// Reads consecutive bytes of several sectors in size.
|
||||
fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> aster_frame::Result<()> {
|
||||
if offset % SECTOR_SIZE != 0 || buf.len() % SECTOR_SIZE != 0 {
|
||||
return Err(aster_frame::Error::InvalidArgs);
|
||||
}
|
||||
if buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (bio, bio_segment) = {
|
||||
let num_blocks = {
|
||||
let first = Bid::from_offset(offset).to_raw();
|
||||
let last = Bid::from_offset(offset + buf.len() - 1).to_raw();
|
||||
last - first + 1
|
||||
};
|
||||
let segment = VmAllocOptions::new(num_blocks as usize)
|
||||
.uninit(true)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()?;
|
||||
let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, buf.len());
|
||||
|
||||
(
|
||||
Bio::new(
|
||||
BioType::Read,
|
||||
Sid::from_offset(offset),
|
||||
vec![bio_segment.clone()],
|
||||
None,
|
||||
),
|
||||
bio_segment,
|
||||
)
|
||||
};
|
||||
|
||||
let status = bio.submit_sync(self)?;
|
||||
match status {
|
||||
BioStatus::Complete => {
|
||||
let _ = bio_segment.reader().read(&mut buf.into());
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(aster_frame::Error::IoError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes consecutive bytes of several sectors in size.
|
||||
fn write_bytes(&self, offset: usize, buf: &[u8]) -> aster_frame::Result<()> {
|
||||
if offset % SECTOR_SIZE != 0 || buf.len() % SECTOR_SIZE != 0 {
|
||||
return Err(aster_frame::Error::InvalidArgs);
|
||||
}
|
||||
if buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let bio = {
|
||||
let num_blocks = {
|
||||
let first = Bid::from_offset(offset).to_raw();
|
||||
let last = Bid::from_offset(offset + buf.len() - 1).to_raw();
|
||||
last - first + 1
|
||||
};
|
||||
let segment = VmAllocOptions::new(num_blocks as usize)
|
||||
.uninit(true)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()?;
|
||||
segment.write_bytes(offset % BLOCK_SIZE, buf)?;
|
||||
let len = segment
|
||||
.writer()
|
||||
.skip(offset % BLOCK_SIZE)
|
||||
.write(&mut buf.into());
|
||||
let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, len);
|
||||
Bio::new(
|
||||
BioType::Write,
|
||||
Sid::from_offset(offset),
|
||||
vec![bio_segment],
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
let status = bio.submit_sync(self)?;
|
||||
match status {
|
||||
BioStatus::Complete => Ok(()),
|
||||
_ => Err(aster_frame::Error::IoError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn BlockDevice {
|
||||
/// Asynchronously writes consecutive bytes of several sectors in size.
|
||||
pub fn write_bytes_async(&self, offset: usize, buf: &[u8]) -> aster_frame::Result<BioWaiter> {
|
||||
if offset % SECTOR_SIZE != 0 || buf.len() % SECTOR_SIZE != 0 {
|
||||
return Err(aster_frame::Error::InvalidArgs);
|
||||
}
|
||||
if buf.is_empty() {
|
||||
return Ok(BioWaiter::new());
|
||||
}
|
||||
|
||||
let bio = {
|
||||
let num_blocks = {
|
||||
let first = Bid::from_offset(offset).to_raw();
|
||||
let last = Bid::from_offset(offset + buf.len() - 1).to_raw();
|
||||
last - first + 1
|
||||
};
|
||||
let segment = VmAllocOptions::new(num_blocks as usize)
|
||||
.uninit(true)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()?;
|
||||
segment.write_bytes(offset % BLOCK_SIZE, buf)?;
|
||||
let len = segment
|
||||
.writer()
|
||||
.skip(offset % BLOCK_SIZE)
|
||||
.write(&mut buf.into());
|
||||
let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, len);
|
||||
Bio::new(
|
||||
BioType::Write,
|
||||
Sid::from_offset(offset),
|
||||
vec![bio_segment],
|
||||
Some(general_complete_fn),
|
||||
)
|
||||
};
|
||||
|
||||
let complete = bio.submit(self)?;
|
||||
Ok(complete)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe we should have a builder for `Bio`.
|
||||
fn create_bio_from_segment(type_: BioType, bid: Bid, segment: &VmSegment) -> Bio {
|
||||
let bio_segment = BioSegment::from_segment(segment.clone(), 0, segment.nbytes());
|
||||
Bio::new(
|
||||
type_,
|
||||
Sid::from(bid),
|
||||
vec![bio_segment],
|
||||
Some(general_complete_fn),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Maybe we should have a builder for `Bio`.
|
||||
fn create_bio_from_frame(type_: BioType, bid: Bid, frame: &VmFrame) -> Bio {
|
||||
let bio_segment = BioSegment::from_frame(frame.clone(), 0, BLOCK_SIZE);
|
||||
Bio::new(
|
||||
type_,
|
||||
Sid::from(bid),
|
||||
vec![bio_segment],
|
||||
Some(general_complete_fn),
|
||||
)
|
||||
}
|
||||
|
||||
fn general_complete_fn(bio: &SubmittedBio) {
|
||||
match bio.status() {
|
||||
BioStatus::Complete => (),
|
||||
err_status => log::error!(
|
||||
"faild to do {:?} on the device with error status: {:?}",
|
||||
bio.type_(),
|
||||
err_status
|
||||
),
|
||||
}
|
||||
}
|
@ -1,32 +1,67 @@
|
||||
//! The block devices of Asterinas.
|
||||
//!
|
||||
//!This crate provides a number of base components for block devices, including
|
||||
//! an abstraction of block devices, as well as the registration and lookup of block devices.
|
||||
//!
|
||||
//! Block devices use a queue-based model for asynchronous I/O operations. It is necessary
|
||||
//! for a block device to maintain a queue to handle I/O requests. The users (e.g., fs)
|
||||
//! submit I/O requests to this queue and wait for their completion. Drivers implementing
|
||||
//! block devices can create their own queues as needed, with the possibility to reorder
|
||||
//! and merge requests within the queue.
|
||||
//!
|
||||
//! This crate also offers the `Bio` related data structures and APIs to accomplish
|
||||
//! safe and convenient block I/O operations, for exmaple:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! // Creates a bio request.
|
||||
//! let bio = Bio::new(BioType::Write, sid, segments, None);
|
||||
//! // Submits to the block device.
|
||||
//! let bio_waiter = bio.submit(block_device)?;
|
||||
//! // Waits for the the completion.
|
||||
//! let Some(status) = bio_waiter.wait() else {
|
||||
//! return Err(IoError);
|
||||
//! };
|
||||
//! assert!(status == BioStatus::Complete);
|
||||
//! ```
|
||||
//!
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(step_trait)]
|
||||
#![feature(trait_upcasting)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::any::Any;
|
||||
use core::fmt::Debug;
|
||||
pub mod bio;
|
||||
pub mod id;
|
||||
mod impl_block_device;
|
||||
mod prelude;
|
||||
pub mod request_queue;
|
||||
|
||||
use self::{prelude::*, request_queue::BioRequestQueue};
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use aster_frame::sync::SpinLock;
|
||||
use aster_frame::vm::VmReader;
|
||||
use aster_frame::vm::VmWriter;
|
||||
use component::init_component;
|
||||
use component::ComponentInitError;
|
||||
|
||||
use spin::Once;
|
||||
|
||||
pub const BLK_SIZE: usize = 512;
|
||||
pub const BLOCK_SIZE: usize = aster_frame::config::PAGE_SIZE;
|
||||
pub const SECTOR_SIZE: usize = 512;
|
||||
|
||||
pub trait BlockDevice: Send + Sync + Any + Debug {
|
||||
fn read_block(&self, block_id: usize, buf: &[VmWriter]);
|
||||
fn write_block(&self, block_id: usize, buf: &[VmReader]);
|
||||
/// Returns this block device's request queue, to which block I/O requests may be submitted.
|
||||
fn request_queue(&self) -> &dyn BioRequestQueue;
|
||||
fn handle_irq(&self);
|
||||
}
|
||||
|
||||
impl dyn BlockDevice {
|
||||
pub fn downcast_ref<T: BlockDevice>(&self) -> Option<&T> {
|
||||
(self as &dyn Any).downcast_ref::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_device(name: String, device: Arc<dyn BlockDevice>) {
|
||||
COMPONENT
|
||||
.get()
|
||||
|
9
services/comps/block/src/prelude.rs
Normal file
9
services/comps/block/src/prelude.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub(crate) use alloc::collections::{BTreeMap, VecDeque};
|
||||
pub(crate) use alloc::string::String;
|
||||
pub(crate) use alloc::sync::Arc;
|
||||
pub(crate) use alloc::vec;
|
||||
pub(crate) use alloc::vec::Vec;
|
||||
pub(crate) use core::any::Any;
|
||||
pub(crate) use core::fmt::Debug;
|
||||
pub(crate) use core::ops::Range;
|
||||
pub(crate) use core::sync::atomic::{AtomicU32, Ordering};
|
93
services/comps/block/src/request_queue.rs
Normal file
93
services/comps/block/src/request_queue.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::{
|
||||
bio::{BioEnqueueError, BioType, SubmittedBio},
|
||||
id::Sid,
|
||||
};
|
||||
|
||||
/// Represents the software staging queue for the `BioRequest` objects.
|
||||
pub trait BioRequestQueue {
|
||||
/// Enqueues a `SubmittedBio` to this queue.
|
||||
///
|
||||
/// This `SubmittedBio` will be merged into an existing `BioRequest`, or a new
|
||||
/// `BioRequest` will be created from the `SubmittedBio` before being placed
|
||||
/// into the queue.
|
||||
///
|
||||
/// This method will wake up the waiter if a new `BioRequest` is enqueued.
|
||||
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError>;
|
||||
|
||||
/// Dequeues a `BioRequest` from this queue.
|
||||
///
|
||||
/// This method will wait until one request can be retrieved.
|
||||
fn dequeue(&self) -> BioRequest;
|
||||
}
|
||||
|
||||
/// The block I/O request.
|
||||
pub struct BioRequest {
|
||||
/// The type of the I/O
|
||||
type_: BioType,
|
||||
/// The range of target sectors on the device
|
||||
sid_range: Range<Sid>,
|
||||
/// The submitted bios
|
||||
bios: VecDeque<SubmittedBio>,
|
||||
}
|
||||
|
||||
impl BioRequest {
|
||||
/// Returns the type of the I/O.
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.type_
|
||||
}
|
||||
|
||||
/// Returns the range of sector id on device.
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
&self.sid_range
|
||||
}
|
||||
|
||||
/// Returns an iterator to the `SubmittedBio`s.
|
||||
pub fn bios(&self) -> impl Iterator<Item = &SubmittedBio> {
|
||||
self.bios.iter()
|
||||
}
|
||||
|
||||
/// Returns `true` if can merge the `SubmittedBio`, `false` otherwise.
|
||||
pub fn can_merge(&self, rq_bio: &SubmittedBio) -> bool {
|
||||
if rq_bio.type_() != self.type_ {
|
||||
return false;
|
||||
}
|
||||
|
||||
rq_bio.sid_range().start == self.sid_range.end
|
||||
|| rq_bio.sid_range().end == self.sid_range.start
|
||||
}
|
||||
|
||||
/// Merges the `SubmittedBio` into this request.
|
||||
///
|
||||
/// The merged `SubmittedBio` can only be placed at the front or back.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `SubmittedBio` can not be merged, this method will panic.
|
||||
pub fn merge_bio(&mut self, rq_bio: SubmittedBio) {
|
||||
assert!(self.can_merge(&rq_bio));
|
||||
|
||||
if rq_bio.sid_range().start == self.sid_range.end {
|
||||
self.sid_range.end = rq_bio.sid_range().end;
|
||||
self.bios.push_back(rq_bio);
|
||||
} else {
|
||||
self.sid_range.start = rq_bio.sid_range().start;
|
||||
self.bios.push_front(rq_bio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SubmittedBio> for BioRequest {
|
||||
fn from(bio: SubmittedBio) -> Self {
|
||||
Self {
|
||||
type_: bio.type_(),
|
||||
sid_range: bio.sid_range().clone(),
|
||||
bios: {
|
||||
let mut bios = VecDeque::with_capacity(1);
|
||||
bios.push_front(bio);
|
||||
bios
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +1,132 @@
|
||||
use core::{hint::spin_loop, mem::size_of};
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
hint::spin_loop,
|
||||
mem::size_of,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc};
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
|
||||
use alloc::{boxed::Box, collections::VecDeque, string::ToString, sync::Arc, vec::Vec};
|
||||
use aster_block::{
|
||||
bio::{BioEnqueueError, BioStatus, BioType, SubmittedBio},
|
||||
id::Sid,
|
||||
request_queue::{BioRequest, BioRequestQueue},
|
||||
};
|
||||
use aster_frame::{
|
||||
io_mem::IoMem,
|
||||
sync::SpinLock,
|
||||
sync::{Mutex, WaitQueue},
|
||||
trap::TrapFrame,
|
||||
vm::{VmAllocOptions, VmFrame, VmIo, VmReader, VmWriter},
|
||||
};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use log::info;
|
||||
use pod::Pod;
|
||||
|
||||
use crate::{
|
||||
device::block::{BlkReq, BlkResp, ReqType, RespStatus},
|
||||
device::block::{ReqType, RespStatus},
|
||||
device::VirtioDeviceError,
|
||||
queue::VirtQueue,
|
||||
transport::VirtioTransport,
|
||||
};
|
||||
|
||||
use super::{BlkFeatures, VirtioBlkConfig};
|
||||
use super::{BlockFeatures, VirtioBlockConfig};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BlockDevice {
|
||||
config: SafePtr<VirtioBlkConfig, IoMem>,
|
||||
device: DeviceInner,
|
||||
/// The software staging queue.
|
||||
queue: BioSingleQueue,
|
||||
}
|
||||
|
||||
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: BioSingleQueue::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 request_queue(&self) -> &dyn BioRequestQueue {
|
||||
&self.queue
|
||||
}
|
||||
|
||||
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
|
||||
@ -34,109 +138,10 @@ pub struct BlockDevice {
|
||||
id_allocator: SpinLock<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl BlockDevice {
|
||||
/// read data from block device, this function is blocking
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn read(&self, block_id: usize, buf: &[VmWriter]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlkReq {
|
||||
type_: ReqType::In as _,
|
||||
reserved: 0,
|
||||
sector: block_id as u64,
|
||||
};
|
||||
let resp = BlkResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlkReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlkResp>(), &resp)
|
||||
.unwrap();
|
||||
let req = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlkReq>())
|
||||
.limit(size_of::<BlkReq>());
|
||||
let resp = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlkResp>())
|
||||
.limit(size_of::<BlkResp>());
|
||||
|
||||
let mut outputs: Vec<&VmWriter<'_>> = buf.iter().collect();
|
||||
outputs.push(&resp);
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let token = queue
|
||||
.add_vm(&[&req], 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: BlkResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlkResp>())
|
||||
.unwrap();
|
||||
self.id_allocator.lock().push(id as u8);
|
||||
match RespStatus::try_from(resp.status).unwrap() {
|
||||
RespStatus::Ok => {}
|
||||
_ => panic!("io error in block device"),
|
||||
};
|
||||
}
|
||||
/// write data to block device, this function is blocking
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn write(&self, block_id: usize, buf: &[VmReader]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlkReq {
|
||||
type_: ReqType::Out as _,
|
||||
reserved: 0,
|
||||
sector: block_id as u64,
|
||||
};
|
||||
let resp = BlkResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlkReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlkResp>(), &resp)
|
||||
.unwrap();
|
||||
let req = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlkReq>())
|
||||
.limit(size_of::<BlkReq>());
|
||||
let resp = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlkResp>())
|
||||
.limit(size_of::<BlkResp>());
|
||||
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let mut inputs: Vec<&VmReader<'_>> = buf.iter().collect();
|
||||
inputs.insert(0, &req);
|
||||
let token = queue
|
||||
.add_vm(inputs.as_slice(), &[&resp])
|
||||
.expect("add queue failed");
|
||||
queue.notify();
|
||||
while !queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
queue.pop_used_with_token(token).expect("pop used failed");
|
||||
let resp: BlkResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlkResp>())
|
||||
.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),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a new VirtIO-Block driver.
|
||||
pub(crate) fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let config = VirtioBlkConfig::new(transport.as_mut());
|
||||
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));
|
||||
@ -170,30 +175,225 @@ impl BlockDevice {
|
||||
info!("Virtio block device config space change");
|
||||
}
|
||||
device.transport.finish_init();
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
aster_block::register_device(super::DEVICE_NAME.to_string(), Arc::new(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 _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple block I/O request queue with a single queue.
|
||||
///
|
||||
/// It is a FIFO producer-consumer queue, where the producer (e.g., filesystem)
|
||||
/// submits requests to the queue, and the consumer (e.g., block device driver)
|
||||
/// continuously consumes and processes these requests from the queue.
|
||||
pub struct BioSingleQueue {
|
||||
queue: Mutex<VecDeque<BioRequest>>,
|
||||
num_requests: AtomicUsize,
|
||||
wait_queue: WaitQueue,
|
||||
}
|
||||
|
||||
impl BioSingleQueue {
|
||||
/// Creates an empty queue.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue: Mutex::new(VecDeque::new()),
|
||||
num_requests: AtomicUsize::new(0),
|
||||
wait_queue: WaitQueue::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of requests currently in this queue.
|
||||
pub fn num_requests(&self) -> usize {
|
||||
self.num_requests.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn dec_num_requests(&self) {
|
||||
self.num_requests.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn inc_num_requests(&self) {
|
||||
self.num_requests.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BioSingleQueue {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BioSingleQueue {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
f.debug_struct("BioSingleQueue")
|
||||
.field("num_requests", &self.num_requests())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BioRequestQueue for BioSingleQueue {
|
||||
/// Enqueues a `SubmittedBio` to this queue.
|
||||
///
|
||||
/// When enqueueing the `SubmittedBio`, try to insert it into the last request if the
|
||||
/// type is same and the sector range is contiguous.
|
||||
/// Otherwise, creates and inserts a new request for the `SubmittedBio`.
|
||||
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> {
|
||||
let mut queue = self.queue.lock();
|
||||
if let Some(request) = queue.front_mut() {
|
||||
if request.can_merge(&bio) {
|
||||
request.merge_bio(bio);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let new_request = BioRequest::from(bio);
|
||||
queue.push_front(new_request);
|
||||
drop(queue);
|
||||
self.inc_num_requests();
|
||||
self.wait_queue.wake_all();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Negotiate features for the device specified bits 0~23
|
||||
pub(crate) fn negotiate_features(features: u64) -> u64 {
|
||||
let feature = BlkFeatures::from_bits(features).unwrap();
|
||||
let support_features = BlkFeatures::from_bits(features).unwrap();
|
||||
(feature & support_features).bits
|
||||
}
|
||||
}
|
||||
|
||||
impl aster_block::BlockDevice for BlockDevice {
|
||||
fn read_block(&self, block_id: usize, buf: &[VmWriter]) {
|
||||
self.read(block_id, buf);
|
||||
}
|
||||
|
||||
fn write_block(&self, block_id: usize, buf: &[VmReader]) {
|
||||
self.write(block_id, buf);
|
||||
}
|
||||
|
||||
fn handle_irq(&self) {
|
||||
info!("Virtio block device handle irq");
|
||||
/// Dequeues a `BioRequest` from this queue.
|
||||
fn dequeue(&self) -> BioRequest {
|
||||
let mut num_requests = self.num_requests();
|
||||
|
||||
loop {
|
||||
if num_requests > 0 {
|
||||
if let Some(request) = self.queue.lock().pop_back() {
|
||||
self.dec_num_requests();
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
num_requests = self.wait_queue.wait_until(|| {
|
||||
let num_requests = self.num_requests();
|
||||
if num_requests > 0 {
|
||||
Some(num_requests)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,11 @@ use pod::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
pub const BLK_SIZE: usize = 512;
|
||||
pub static DEVICE_NAME: &str = "Virtio-Block";
|
||||
|
||||
bitflags! {
|
||||
/// features for virtio block device
|
||||
pub(crate) struct BlkFeatures : u64{
|
||||
pub(crate) struct BlockFeatures : u64 {
|
||||
const BARRIER = 1 << 0;
|
||||
const SIZE_MAX = 1 << 1;
|
||||
const SEG_MAX = 1 << 2;
|
||||
@ -29,29 +28,6 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
pub struct BlkReq {
|
||||
pub type_: u32,
|
||||
pub reserved: u32,
|
||||
pub sector: u64,
|
||||
}
|
||||
|
||||
/// Response of a VirtIOBlk request.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
pub struct BlkResp {
|
||||
pub status: u8,
|
||||
}
|
||||
|
||||
impl Default for BlkResp {
|
||||
fn default() -> Self {
|
||||
BlkResp {
|
||||
status: RespStatus::_NotReady as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone, TryFromInt)]
|
||||
pub enum ReqType {
|
||||
@ -77,12 +53,12 @@ pub enum RespStatus {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlkConfig {
|
||||
pub struct VirtioBlockConfig {
|
||||
capacity: u64,
|
||||
size_max: u64,
|
||||
geometry: VirtioBlkGeometry,
|
||||
geometry: VirtioBlockGeometry,
|
||||
blk_size: u32,
|
||||
topology: VirtioBlkTopology,
|
||||
topology: VirtioBlockTopology,
|
||||
writeback: u8,
|
||||
unused0: [u8; 3],
|
||||
max_discard_sectors: u32,
|
||||
@ -96,7 +72,7 @@ pub struct VirtioBlkConfig {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlkGeometry {
|
||||
pub struct VirtioBlockGeometry {
|
||||
cylinders: u16,
|
||||
heads: u8,
|
||||
sectors: u8,
|
||||
@ -104,14 +80,14 @@ pub struct VirtioBlkGeometry {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlkTopology {
|
||||
pub struct VirtioBlockTopology {
|
||||
physical_block_exp: u8,
|
||||
alignment_offset: u8,
|
||||
min_io_size: u16,
|
||||
opt_io_size: u32,
|
||||
}
|
||||
|
||||
impl VirtioBlkConfig {
|
||||
impl VirtioBlockConfig {
|
||||
pub(self) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
|
Reference in New Issue
Block a user