mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-26 10:53:25 +00:00
Refactor project structure
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
bd878dd1c9
commit
e3c227ae06
23
kernel/comps/block/Cargo.toml
Normal file
23
kernel/comps/block/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "aster-block"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[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]
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.0"
|
||||
features = ["spin_no_std"]
|
488
kernel/comps/block/src/bio.rs
Normal file
488
kernel/comps/block/src/bio.rs
Normal file
@ -0,0 +1,488 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_frame::{
|
||||
sync::WaitQueue,
|
||||
vm::{VmFrame, VmReader, VmSegment, VmWriter},
|
||||
};
|
||||
use int_to_c_enum::TryFromInt;
|
||||
|
||||
use super::{id::Sid, BlockDevice};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug)]
|
||||
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.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]
|
||||
#[derive(Debug)]
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BioInner {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
f.debug_struct("BioInner")
|
||||
.field("type", &self.type_())
|
||||
.field("sid_range", &self.sid_range())
|
||||
.field("status", &self.status())
|
||||
.field("segments", &self.segments())
|
||||
.field("complete_fn", &self.complete_fn)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
103
kernel/comps/block/src/id.rs
Normal file
103
kernel/comps/block/src/id.rs
Normal file
@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
238
kernel/comps/block/src/impl_block_device.rs
Normal file
238
kernel/comps/block/src/impl_block_device.rs
Normal file
@ -0,0 +1,238 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_frame::vm::{VmAllocOptions, VmFrame, VmIo, VmSegment};
|
||||
|
||||
use super::{
|
||||
bio::{Bio, BioEnqueueError, BioSegment, BioStatus, BioType, BioWaiter, SubmittedBio},
|
||||
id::{Bid, Sid},
|
||||
BlockDevice, BLOCK_SIZE, SECTOR_SIZE,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// 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
|
||||
),
|
||||
}
|
||||
}
|
114
kernel/comps/block/src/lib.rs
Normal file
114
kernel/comps/block/src/lib.rs
Normal file
@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! 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)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod bio;
|
||||
pub mod id;
|
||||
mod impl_block_device;
|
||||
mod prelude;
|
||||
pub mod request_queue;
|
||||
|
||||
use aster_frame::sync::SpinLock;
|
||||
use component::{init_component, ComponentInitError};
|
||||
use spin::Once;
|
||||
|
||||
use self::{
|
||||
bio::{BioEnqueueError, SubmittedBio},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
pub const BLOCK_SIZE: usize = aster_frame::config::PAGE_SIZE;
|
||||
pub const SECTOR_SIZE: usize = 512;
|
||||
|
||||
pub trait BlockDevice: Send + Sync + Any + Debug {
|
||||
/// Enqueues a new `SubmittedBio` to the block device.
|
||||
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError>;
|
||||
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()
|
||||
.unwrap()
|
||||
.block_device_table
|
||||
.lock()
|
||||
.insert(name, device);
|
||||
}
|
||||
|
||||
pub fn get_device(str: &str) -> Option<Arc<dyn BlockDevice>> {
|
||||
COMPONENT
|
||||
.get()
|
||||
.unwrap()
|
||||
.block_device_table
|
||||
.lock()
|
||||
.get(str)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn all_devices() -> Vec<(String, Arc<dyn BlockDevice>)> {
|
||||
let block_devs = COMPONENT.get().unwrap().block_device_table.lock();
|
||||
block_devs
|
||||
.iter()
|
||||
.map(|(name, device)| (name.clone(), device.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
static COMPONENT: Once<Component> = Once::new();
|
||||
|
||||
#[init_component]
|
||||
fn component_init() -> Result<(), ComponentInitError> {
|
||||
let a = Component::init()?;
|
||||
COMPONENT.call_once(|| a);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Component {
|
||||
block_device_table: SpinLock<BTreeMap<String, Arc<dyn BlockDevice>>>,
|
||||
}
|
||||
|
||||
impl Component {
|
||||
pub fn init() -> Result<Self, ComponentInitError> {
|
||||
Ok(Self {
|
||||
block_device_table: SpinLock::new(BTreeMap::new()),
|
||||
})
|
||||
}
|
||||
}
|
15
kernel/comps/block/src/prelude.rs
Normal file
15
kernel/comps/block/src/prelude.rs
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub(crate) use alloc::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
string::String,
|
||||
sync::Arc,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
pub(crate) use core::{
|
||||
any::Any,
|
||||
fmt::Debug,
|
||||
ops::Range,
|
||||
sync::atomic::{AtomicU32, AtomicUsize, Ordering},
|
||||
};
|
188
kernel/comps/block/src/request_queue.rs
Normal file
188
kernel/comps/block/src/request_queue.rs
Normal file
@ -0,0 +1,188 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aster_frame::sync::{Mutex, WaitQueue};
|
||||
|
||||
use super::{
|
||||
bio::{BioEnqueueError, BioType, SubmittedBio},
|
||||
id::Sid,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A simple block I/O request queue backed by one internal FIFO 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.
|
||||
///
|
||||
/// It supports merging the new request with the front request if if the type
|
||||
/// is same and the sector range is contiguous.
|
||||
pub struct BioRequestSingleQueue {
|
||||
queue: Mutex<VecDeque<BioRequest>>,
|
||||
num_requests: AtomicUsize,
|
||||
wait_queue: WaitQueue,
|
||||
}
|
||||
|
||||
impl BioRequestSingleQueue {
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
///
|
||||
/// This method will wake up the waiter if a new `BioRequest` is enqueued.
|
||||
pub 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);
|
||||
self.inc_num_requests();
|
||||
drop(queue);
|
||||
|
||||
self.wait_queue.wake_all();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dequeues a `BioRequest` from this queue.
|
||||
///
|
||||
/// This method will wait until one request can be retrieved.
|
||||
pub fn dequeue(&self) -> BioRequest {
|
||||
let mut num_requests = self.num_requests();
|
||||
|
||||
loop {
|
||||
if num_requests > 0 {
|
||||
let mut queue = self.queue.lock();
|
||||
if let Some(request) = queue.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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 BioRequestSingleQueue {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BioRequestSingleQueue {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
f.debug_struct("BioRequestSingleQueue")
|
||||
.field("num_requests", &self.num_requests())
|
||||
.field("queue", &self.queue.lock())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// The block I/O request.
|
||||
///
|
||||
/// The advantage of this data structure is to merge several `SubmittedBio`s that are
|
||||
/// contiguous on the target device's sector address, allowing them to be collectively
|
||||
/// processed in a queue.
|
||||
#[derive(Debug)]
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user