mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-27 19:33:23 +00:00
Add Ext2 fs and basic bio layer
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
1616f2d32c
commit
9473889c6b
@ -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