diff --git a/Cargo.lock b/Cargo.lock index 84e521f66..f456f8120 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,8 @@ dependencies = [ "aster-util", "bitvec", "component", + "device-id", + "id-alloc", "int-to-c-enum", "log", "ostd", @@ -171,6 +173,7 @@ dependencies = [ "bittle", "component", "ctr", + "device-id", "hashbrown 0.14.5", "inherit-methods-macro", "lending-iterator", @@ -351,6 +354,7 @@ dependencies = [ "aster-util", "bitflags 1.3.2", "component", + "device-id", "id-alloc", "int-to-c-enum", "log", @@ -670,6 +674,9 @@ dependencies = [ [[package]] name = "device-id" version = "0.1.0" +dependencies = [ + "aster-util", +] [[package]] name = "either" diff --git a/Makefile b/Makefile index e8cc6b59a..6c561360f 100644 --- a/Makefile +++ b/Makefile @@ -183,7 +183,6 @@ NON_OSDK_CRATES := \ kernel/libs/aster-rights-proc \ kernel/libs/atomic-integer-wrapper \ kernel/libs/cpio-decoder \ - kernel/libs/device-id \ kernel/libs/int-to-c-enum \ kernel/libs/int-to-c-enum/derive \ kernel/libs/jhash \ @@ -216,6 +215,7 @@ OSDK_CRATES := \ kernel/comps/pci \ kernel/libs/aster-util \ kernel/libs/aster-bigtcp \ + kernel/libs/device-id \ kernel/libs/xarray # OSDK dependencies diff --git a/kernel/comps/block/Cargo.toml b/kernel/comps/block/Cargo.toml index f35a3a860..281595987 100644 --- a/kernel/comps/block/Cargo.toml +++ b/kernel/comps/block/Cargo.toml @@ -9,9 +9,11 @@ edition = "2021" spin = "0.9.4" ostd = { path = "../../../ostd" } align_ext = { path = "../../../ostd/libs/align_ext" } +id-alloc = { path = "../../../ostd/libs/id-alloc" } int-to-c-enum = { path = "../../libs/int-to-c-enum" } aster-util = { path = "../../libs/aster-util" } component = { path = "../../libs/comp-sys/component" } +device-id = { path = "../../libs/device-id" } log = "0.4" bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } diff --git a/kernel/comps/block/src/bio.rs b/kernel/comps/block/src/bio.rs index 7a61119be..b19e662bd 100644 --- a/kernel/comps/block/src/bio.rs +++ b/kernel/comps/block/src/bio.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use core::sync::atomic::AtomicU64; + use align_ext::AlignExt; use aster_util::mem_obj_slice::Slice; use bitvec::array::BitArray; @@ -49,6 +51,7 @@ impl Bio { let inner = Arc::new(BioInner { type_, sid_range: start_sid..start_sid + nsectors, + sid_offset: AtomicU64::new(0), segments, complete_fn, status: AtomicU32::new(BioStatus::Init as u32), @@ -258,6 +261,16 @@ impl SubmittedBio { self.0.sid_range() } + /// Returns the offset of the first sector id. + pub fn sid_offset(&self) -> u64 { + self.0.sid_offset.load(Ordering::Relaxed) + } + + /// Sets the offset of the first sector id. + pub fn set_sid_offset(&self, offset: u64) { + self.0.sid_offset.store(offset, Ordering::Relaxed); + } + /// Returns the slice to the memory segments. pub fn segments(&self) -> &[BioSegment] { self.0.segments() @@ -294,8 +307,10 @@ impl SubmittedBio { struct BioInner { /// The type of the I/O type_: BioType, - /// The range of the sector id on device + /// The logical range of target sectors on device sid_range: Range, + /// The offset of the first sector id, used to adjust the `sid_range` for partition devices + sid_offset: AtomicU64, /// The memory segments in this `Bio` segments: Vec, /// The I/O completion method diff --git a/kernel/comps/block/src/device_id.rs b/kernel/comps/block/src/device_id.rs new file mode 100644 index 000000000..df23d44d6 --- /dev/null +++ b/kernel/comps/block/src/device_id.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::collections::btree_set::BTreeSet; + +use device_id::{DeviceId, MajorId, MinorId}; +use id_alloc::IdAlloc; +use ostd::sync::Mutex; +use spin::Once; + +use crate::Error; + +/// The maximum value of the major device ID of a block device. +/// +/// Reference: . +pub const MAX_MAJOR: u16 = 511; + +/// Block devices that request a dynamic allocation of major ID will +/// take numbers starting from 254 and downward. +/// +/// Reference: . +const LAST_DYNAMIC_MAJOR: u16 = 254; + +static MAJORS: Mutex> = Mutex::new(BTreeSet::new()); + +/// Acquires a major ID. +/// +/// The returned `MajorIdOwner` object represents the ownership to the major ID. +/// Until the object is dropped, this major ID cannot be acquired via `acquire_major` or `allocate_major` again. +pub fn acquire_major(major: MajorId) -> Result { + if major.get() > MAX_MAJOR { + return Err(Error::InvalidArgs); + } + + if MAJORS.lock().insert(major.get()) { + Ok(MajorIdOwner(major)) + } else { + Err(Error::IdAcquired) + } +} + +/// Allocates a major ID. +/// +/// The returned `MajorIdOwner` object represents the ownership to the major ID. +/// Until the object is dropped, this major ID cannot be acquired via `acquire_major` or `allocate_major` again. +pub fn allocate_major() -> Result { + let mut majors = MAJORS.lock(); + for id in (1..LAST_DYNAMIC_MAJOR + 1).rev() { + if majors.insert(id) { + return Ok(MajorIdOwner(MajorId::new(id))); + } + } + + Err(Error::IdExhausted) +} + +/// An owned major ID. +/// +/// Each instances of this type will unregister the major ID when dropped. +pub struct MajorIdOwner(MajorId); + +impl MajorIdOwner { + /// Returns the major ID. + pub fn get(&self) -> MajorId { + self.0 + } +} + +impl Drop for MajorIdOwner { + fn drop(&mut self) { + MAJORS.lock().remove(&self.0.get()); + } +} + +/// The major ID used for extended partitions when the number of disk partitions exceeds the standard limit. +/// +/// Reference: . +const EXTENDED_MAJOR: u16 = 259; + +/// An allocator for extended device IDs. +pub struct ExtendedDeviceIdAllocator { + major: MajorIdOwner, + minor_allocator: Mutex, +} + +impl ExtendedDeviceIdAllocator { + fn new() -> Self { + let major = MajorId::new(EXTENDED_MAJOR); + let minor_allocator = IdAlloc::with_capacity(MinorId::MAX.get() as usize + 1); + + Self { + major: acquire_major(major).unwrap(), + minor_allocator: Mutex::new(minor_allocator), + } + } + + /// Allocates an extended device ID. + pub fn allocate(&self) -> DeviceId { + let minor = self.minor_allocator.lock().alloc().unwrap() as u32; + + DeviceId::new(self.major.get(), MinorId::new(minor)) + } + + /// Releases an extended device ID. + pub fn release(&mut self, id: DeviceId) { + if id.major() != self.major.get() { + return; + } + + self.minor_allocator.lock().free(id.minor().get() as usize); + } +} + +pub static EXTENDED_DEVICE_ID_ALLOCATOR: Once = Once::new(); + +pub(super) fn init() { + EXTENDED_DEVICE_ID_ALLOCATOR.call_once(ExtendedDeviceIdAllocator::new); +} diff --git a/kernel/comps/block/src/lib.rs b/kernel/comps/block/src/lib.rs index cafcbacb5..3a4729d1a 100644 --- a/kernel/comps/block/src/lib.rs +++ b/kernel/comps/block/src/lib.rs @@ -34,14 +34,18 @@ extern crate alloc; pub mod bio; +mod device_id; pub mod id; mod impl_block_device; +mod partition; mod prelude; pub mod request_queue; +use ::device_id::DeviceId; use component::{init_component, ComponentInitError}; -use ostd::sync::SpinLock; -use spin::Once; +pub use device_id::{acquire_major, allocate_major, MajorIdOwner, EXTENDED_DEVICE_ID_ALLOCATOR}; +use ostd::sync::Mutex; +pub use partition::{PartitionInfo, PartitionNode}; use self::{ bio::{BioEnqueueError, SubmittedBio}, @@ -57,10 +61,29 @@ pub trait BlockDevice: Send + Sync + Any + Debug { /// Returns the metadata of the block device. fn metadata(&self) -> BlockDeviceMeta; + + /// Returns the name of the block device. + fn name(&self) -> &str; + + /// Returns the device ID of the block device. + fn id(&self) -> DeviceId; + + /// Returns whether the block device is a partition. + fn is_partition(&self) -> bool { + false + } + + /// Sets the partitions of the block device. + fn set_partitions(&self, _infos: Vec>) {} + + /// Returns the partitions of the block device. + fn partitions(&self) -> Option>> { + None + } } /// Metadata for a block device. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] pub struct BlockDeviceMeta { /// The upper limit for the number of segments per bio. pub max_nr_segments_per_bio: usize, @@ -75,51 +98,70 @@ impl dyn BlockDevice { } } -pub fn register_device(name: String, device: Arc) { - COMPONENT - .get() - .unwrap() - .block_device_table - .lock() - .insert(name, device); +/// The error type which is returned from the APIs of this crate. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Error { + /// Device registered + Registered, + /// Device not found + NotFound, + /// Invalid arguments + InvalidArgs, + /// Id Acquired + IdAcquired, + /// Id Exhausted + IdExhausted, } -pub fn get_device(str: &str) -> Option> { - COMPONENT - .get() - .unwrap() - .block_device_table - .lock() - .get(str) - .cloned() -} +/// Registers a new block device. +pub fn register(device: Arc) -> Result<(), Error> { + let mut registry = DEVICE_REGISTRY.lock(); + let id = device.id().to_raw(); + if registry.contains_key(&id) { + return Err(Error::Registered); + } + registry.insert(id, device); -pub fn all_devices() -> Vec<(String, Arc)> { - let block_devs = COMPONENT.get().unwrap().block_device_table.lock(); - block_devs - .iter() - .map(|(name, device)| (name.clone(), device.clone())) - .collect() -} - -static COMPONENT: Once = 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>>, +/// Unregisters an existing block device, returning the device if found. +pub fn unregister(id: DeviceId) -> Result, Error> { + DEVICE_REGISTRY + .lock() + .remove(&id.to_raw()) + .ok_or(Error::NotFound) } -impl Component { - pub fn init() -> Result { - Ok(Self { - block_device_table: SpinLock::new(BTreeMap::new()), - }) - } +/// Collects all block devices. +pub fn collect_all() -> Vec> { + DEVICE_REGISTRY.lock().values().cloned().collect() +} + +/// Looks up a block device of a given device ID. +pub fn lookup(id: DeviceId) -> Option> { + DEVICE_REGISTRY.lock().get(&id.to_raw()).cloned() +} + +static DEVICE_REGISTRY: Mutex>> = Mutex::new(BTreeMap::new()); + +#[init_component] +fn init() -> Result<(), ComponentInitError> { + device_id::init(); + + Ok(()) +} + +#[init_component(process)] +fn init_in_first_process() -> Result<(), component::ComponentInitError> { + let devices = collect_all(); + for device in devices { + let Some(partition_info) = partition::parse(&device) else { + continue; + }; + + device.set_partitions(partition_info); + } + + Ok(()) } diff --git a/kernel/comps/block/src/partition.rs b/kernel/comps/block/src/partition.rs new file mode 100644 index 000000000..b618183a1 --- /dev/null +++ b/kernel/comps/block/src/partition.rs @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MPL-2.0 + +use device_id::DeviceId; +use ostd::{mm::VmIo, Pod}; + +use crate::{ + bio::{BioEnqueueError, SubmittedBio}, + prelude::*, + BlockDevice, BlockDeviceMeta, SECTOR_SIZE, +}; + +/// Represents a partition entry. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum PartitionInfo { + Mbr(MbrEntry), + Gpt(GptEntry), +} + +impl PartitionInfo { + pub fn start_sector(&self) -> u64 { + match self { + PartitionInfo::Mbr(entry) => entry.start_sector as u64, + PartitionInfo::Gpt(entry) => entry.start_lba, + } + } + + pub fn total_sectors(&self) -> u64 { + match self { + PartitionInfo::Mbr(entry) => entry.total_sectors as u64, + PartitionInfo::Gpt(entry) => entry.end_lba - entry.start_lba + 1, + } + } +} + +/// A MBR (Master Boot Record) partition table header. +/// +/// See . +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod)] +struct MbrHeader { + bootstrap_code: [u8; 440], + id: u32, + reserved: u16, + entries: [MbrEntry; 4], + signature: u16, +} + +impl MbrHeader { + fn check_signature(&self) -> bool { + self.signature == 0xAA55 + } +} + +/// A MBR (Master Boot Record) partition entry. +/// +/// See . +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Pod)] +pub struct MbrEntry { + flag: u8, + start_chs: ChsAddr, + type_: u8, + end_chs: ChsAddr, + start_sector: u32, + total_sectors: u32, +} + +impl MbrEntry { + fn is_extended(&self) -> bool { + self.type_ == 0x05 || self.type_ == 0x0F + } + + fn is_valid(&self) -> bool { + // A System ID byte value of 0 is the definitive indicator for an unused entry. + // Any other illegal value (CHS Sector = 0 or Total Sectors = 0) may also indicate an unused entry. + self.type_ != 0x00 + && self.start_chs.0[1] != 0 + && self.end_chs.0[1] != 0 + && self.total_sectors != 0 + } +} + +/// A CHS (Cylinder-Head-Sector) address. +/// +/// In CHS addressing, sector numbers always start at 1; there is no sector 0. +/// +/// The CHS address is stored as a 3-byte field: +/// - Byte 0: Head number (8 bits) +/// - Byte 1: Bits 0–5 are the sector number (6 bits, valid values 1–63); +/// bits 6–7 are the upper two bits of the cylinder number +/// - Byte 2: Lower 8 bits of the cylinder number (bits 0–7) +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Pod)] +struct ChsAddr([u8; 3]); + +/// A GPT (GUID Partition Table) header. +/// +/// See . +#[repr(C)] +#[derive(Debug, Copy, Clone, Pod)] +struct GptHeader { + signature: u64, + revision: u32, + size: u32, + crc32: u32, + reserved: u32, + current_lba: u64, + backup_lba: u64, + first_usable_lba: u64, + last_usable_lba: u64, + guid: [u8; 16], + partition_entry_lba: u64, + nr_partition_entries: u32, + size_of_partition_entry: u32, + crc32_of_partition_entries: u32, + _padding: [u8; 420], +} + +impl GptHeader { + fn check_signature(&self) -> bool { + &self.signature.to_le_bytes() == b"EFI PART" + } +} + +/// A GPT (GUID Partition Table) partition entry. +/// +/// See . +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Pod)] +pub struct GptEntry { + // Unique ID that defines the purpose and type of this Partition. + // A value of zero defines that this partition entry is not being used. + type_guid: [u8; 16], + // GUID that is unique for every partition entry. + guid: [u8; 16], + start_lba: u64, + end_lba: u64, + attributes: u64, + // Null-terminated string containing a human-readable name of the partition. + name: [u8; 72], +} + +impl GptEntry { + fn is_valid(&self) -> bool { + self.type_guid != [0; 16] + } +} + +pub(super) fn parse(device: &Arc) -> Option>> { + let mbr = device.read_val::(0).unwrap(); + + // 0xEE indicates a GPT Protective MBR, a fake partition covering the entire disk. + let partitions = if mbr.check_signature() && mbr.entries[0].type_ != 0xEE { + parse_mbr(device, &mbr) + } else { + parse_gpt(device) + }; + + partitions.iter().any(|p| p.is_some()).then_some(partitions) +} + +fn parse_mbr(device: &Arc, mbr: &MbrHeader) -> Vec> { + let mut partitions = Vec::new(); + let mut extended_partition = None; + for entry in mbr.entries { + if entry.is_extended() { + extended_partition = Some(entry.start_sector); + } + + if entry.is_valid() { + partitions.push(Some(PartitionInfo::Mbr(entry))); + } else { + partitions.push(None); + } + } + + if let Some(start_sector) = extended_partition { + parse_ebr(device, &mut partitions, start_sector, 0); + } + + partitions +} + +fn parse_ebr( + device: &Arc, + partitions: &mut Vec>, + start_sector: u32, + offset: u32, +) { + let ebr_sector = start_sector + offset; + let mut ebr = device + .read_val::(ebr_sector as usize * SECTOR_SIZE) + .unwrap(); + if ebr.entries[0].is_valid() { + ebr.entries[0].start_sector += ebr_sector; + partitions.push(Some(PartitionInfo::Mbr(ebr.entries[0]))); + } + + if ebr.entries[1].is_extended() { + parse_ebr( + device, + partitions, + start_sector, + ebr.entries[1].start_sector, + ); + } +} + +fn parse_gpt(device: &Arc) -> Vec> { + let mut partitions = Vec::new(); + + // The primary GPT Header must be located in LBA 1. + let gpt = device.read_val::(SECTOR_SIZE).unwrap(); + + if !gpt.check_signature() { + return partitions; + } + + // TODO: Check the CRC32 of the header and the partition entries, check the backup GPT header. + + let entry_size = gpt.size_of_partition_entry as usize; + let entries_per_sector = SECTOR_SIZE / entry_size; + let total_sectors = gpt.nr_partition_entries as usize / entries_per_sector; + for i in 0..total_sectors { + let mut buf = [0u8; SECTOR_SIZE]; + let offset = (gpt.partition_entry_lba as usize + i) * SECTOR_SIZE; + device.read_bytes(offset, buf.as_mut_slice()).unwrap(); + + for j in 0..entries_per_sector { + let entry_offset = j * gpt.size_of_partition_entry as usize; + let entry = GptEntry::from_bytes(&buf[entry_offset..entry_offset + entry_size]); + if entry.is_valid() { + partitions.push(Some(PartitionInfo::Gpt(entry))); + } else { + partitions.push(None); + } + } + } + + partitions +} + +#[derive(Debug)] +pub struct PartitionNode { + id: DeviceId, + name: String, + device: Arc, + info: PartitionInfo, +} + +impl BlockDevice for PartitionNode { + fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> { + bio.set_sid_offset(self.info.start_sector()); + self.device.enqueue(bio) + } + + fn metadata(&self) -> BlockDeviceMeta { + let mut metadata = self.device.metadata(); + metadata.nr_sectors = self.info.total_sectors() as usize; + metadata + } + + fn name(&self) -> &str { + &self.name + } + + fn id(&self) -> DeviceId { + self.id + } +} + +impl PartitionNode { + pub fn new( + id: DeviceId, + name: String, + device: Arc, + info: PartitionInfo, + ) -> Self { + Self { + id, + name, + device, + info, + } + } +} diff --git a/kernel/comps/block/src/request_queue.rs b/kernel/comps/block/src/request_queue.rs index eb0530f4b..4523e637b 100644 --- a/kernel/comps/block/src/request_queue.rs +++ b/kernel/comps/block/src/request_queue.rs @@ -130,16 +130,24 @@ impl Debug for BioRequestSingleQueue { } } -/// The block I/O request. +/// A block I/O request dequeued from [`BioRequestSingleQueue`]. /// -/// 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. +/// This `BioRequest` type is more friendly to storage medium than `SubmittedBio` for two reasons. +/// +/// First, a `BioRequest` can represent a merged request over multiple `SubmittedBio`s +/// that (1) are of the same request type and (2) are contiguous in terms of target sectors. +/// This helps reduce the number of I/O requests submitted to the underlying storage medium. +/// +/// Second, a `BioRequest` provides the physical sector addresses suitable for storage medium. +/// The sector addresses returned from `SubmittedBio::sid_range()` are logical ones: +/// they need to be adjusted with `SubmittedBio::sid_offset()` to calculate the physical ones. +/// This calculation is handled internally by `BioRequest`. +/// One can simply call `BioRequest::sid_range()` to obtain the physical sector addresses. #[derive(Debug)] pub struct BioRequest { /// The type of the I/O type_: BioType, - /// The range of target sectors on the device + /// The physical range of target sectors on the device sid_range: Range, /// The number of segments num_segments: usize, @@ -181,8 +189,10 @@ impl BioRequest { return false; } - rq_bio.sid_range().start == self.sid_range.end - || rq_bio.sid_range().end == self.sid_range.start + let sid_offset = rq_bio.sid_offset(); + + rq_bio.sid_range().start + sid_offset == self.sid_range.end + || rq_bio.sid_range().end + sid_offset == self.sid_range.start } /// Merges the `SubmittedBio` into this request. @@ -196,12 +206,13 @@ impl BioRequest { assert!(self.can_merge(&rq_bio)); let rq_bio_nr_segments = rq_bio.segments().len(); + let sid_offset = rq_bio.sid_offset(); - if rq_bio.sid_range().start == self.sid_range.end { - self.sid_range.end = rq_bio.sid_range().end; + if rq_bio.sid_range().start + sid_offset == self.sid_range.end { + self.sid_range.end = rq_bio.sid_range().end + sid_offset; self.bios.push_back(rq_bio); } else { - self.sid_range.start = rq_bio.sid_range().start; + self.sid_range.start = rq_bio.sid_range().start + sid_offset; self.bios.push_front(rq_bio); } @@ -211,9 +222,13 @@ impl BioRequest { impl From for BioRequest { fn from(bio: SubmittedBio) -> Self { + let mut sid_range = bio.sid_range().clone(); + sid_range.start = sid_range.start + bio.sid_offset(); + sid_range.end = sid_range.start + bio.sid_offset(); + Self { type_: bio.type_(), - sid_range: bio.sid_range().clone(), + sid_range, num_segments: bio.segments().len(), bios: { let mut bios = VecDeque::with_capacity(1); diff --git a/kernel/comps/mlsdisk/Cargo.toml b/kernel/comps/mlsdisk/Cargo.toml index 6086e34ff..5eb06644a 100644 --- a/kernel/comps/mlsdisk/Cargo.toml +++ b/kernel/comps/mlsdisk/Cargo.toml @@ -10,6 +10,7 @@ inherit-methods-macro = {git = "https://github.com/asterinas/inherit-methods-mac ostd-pod = { git = "https://github.com/asterinas/ostd-pod", rev = "c4644be", version = "0.1.1" } component = { path = "../../libs/comp-sys/component" } aster-block = { path = "../block" } +device-id = { path = "../../libs/device-id" } ostd = { path = "../../../ostd" } # Enable `force-soft` feature to disable `AES-NI` and `CLMUL` intrinsics, ensuring that the implementation # relies solely on software, and in the software implementation, unsafe code is rarely used. diff --git a/kernel/comps/mlsdisk/src/layers/5-disk/mlsdisk.rs b/kernel/comps/mlsdisk/src/layers/5-disk/mlsdisk.rs index fa489cdcd..9ac1cef78 100644 --- a/kernel/comps/mlsdisk/src/layers/5-disk/mlsdisk.rs +++ b/kernel/comps/mlsdisk/src/layers/5-disk/mlsdisk.rs @@ -15,6 +15,7 @@ use core::{ sync::atomic::{AtomicBool, Ordering}, }; +use device_id::DeviceId; use ostd::mm::{HasSize, VmIo}; use ostd_pod::Pod; @@ -163,6 +164,14 @@ impl aster_block::BlockDevice for MlsDisk { nr_sectors: (BLOCK_SIZE / SECTOR_SIZE) * self.total_blocks(), } } + + fn name(&self) -> &str { + todo!() + } + + fn id(&self) -> DeviceId { + todo!() + } } impl MlsDisk { diff --git a/kernel/comps/mlsdisk/src/lib.rs b/kernel/comps/mlsdisk/src/lib.rs index 7c52b70bf..037ac6d42 100644 --- a/kernel/comps/mlsdisk/src/lib.rs +++ b/kernel/comps/mlsdisk/src/lib.rs @@ -5,7 +5,8 @@ #![feature(let_chains)] #![feature(negative_impls)] #![feature(slice_as_chunks)] -#![expect(dead_code, unused_imports)] +#![allow(unfulfilled_lint_expectations)] +#![expect(dead_code, deprecated, unused_imports)] mod error; mod layers; @@ -25,6 +26,7 @@ use aster_block::{ BlockDevice, SECTOR_SIZE, }; use component::{init_component, ComponentInitError}; +use device_id::{DeviceId, MajorId, MinorId}; use ostd::{ mm::{io_util::HasVmReaderWriter, VmIo}, prelude::*, @@ -42,15 +44,16 @@ pub use self::{ #[init_component] fn init() -> core::result::Result<(), ComponentInitError> { - // FIXME: add a virtio-blk-pci device in qemu and a image file. - let Some(device) = aster_block::get_device("raw_mlsdisk") else { + // FIXME: how to find a valid device used to format mlsdisk. + let id = DeviceId::new(MajorId::new(255), MinorId::new(0)); + let Some(device) = aster_block::lookup(id) else { return Err(ComponentInitError::Unknown); }; let raw_disk = RawDisk::new(device); let root_key = AeadKey::random(); let device = MlsDisk::create(raw_disk, root_key, None).map_err(|_| ComponentInitError::Unknown)?; - aster_block::register_device("mlsdisk".to_string(), Arc::new(device)); + let _ = aster_block::register(Arc::new(device)); Ok(()) } @@ -134,6 +137,7 @@ mod test { bio::{BioEnqueueError, BioStatus, BioType, SubmittedBio}, BlockDevice, BlockDeviceMeta, SECTOR_SIZE, }; + use device_id::DeviceId; use ostd::{ mm::{FrameAllocOptions, Segment, VmIo}, prelude::*, @@ -190,6 +194,14 @@ mod test { nr_sectors: self.blocks.size() / SECTOR_SIZE, } } + + fn name(&self) -> &str { + todo!() + } + + fn id(&self) -> DeviceId { + todo!() + } } fn create_rawdisk(nblocks: usize) -> RawDisk { diff --git a/kernel/comps/virtio/Cargo.toml b/kernel/comps/virtio/Cargo.toml index 835541cc7..04559688e 100644 --- a/kernel/comps/virtio/Cargo.toml +++ b/kernel/comps/virtio/Cargo.toml @@ -18,6 +18,7 @@ aster-bigtcp = { path = "../../libs/aster-bigtcp" } aster-pci = { path = "../pci" } aster-softirq = { path = "../softirq"} aster-systree = { path = "../systree" } +device-id = { path = "../../libs/device-id" } id-alloc = { path = "../../../ostd/libs/id-alloc" } typeflags-util = { path = "../../libs/typeflags-util" } ostd = { path = "../../../ostd" } diff --git a/kernel/comps/virtio/src/device/block/device.rs b/kernel/comps/virtio/src/device/block/device.rs index aecc60e1f..58333dbd5 100644 --- a/kernel/comps/virtio/src/device/block/device.rs +++ b/kernel/comps/virtio/src/device/block/device.rs @@ -3,19 +3,23 @@ use alloc::{ boxed::Box, collections::BTreeMap, - string::{String, ToString}, - sync::Arc, - vec, + format, + string::String, + sync::{Arc, Weak}, vec::Vec, }; -use core::{fmt::Debug, hint::spin_loop}; +use core::{ + fmt::Debug, + sync::atomic::{AtomicU32, Ordering}, +}; use aster_block::{ bio::{bio_segment_pool_init, BioEnqueueError, BioStatus, BioType, SubmittedBio}, request_queue::{BioRequest, BioRequestSingleQueue}, - BlockDeviceMeta, + BlockDeviceMeta, PartitionInfo, PartitionNode, EXTENDED_DEVICE_ID_ALLOCATOR, }; use aster_util::mem_obj_slice::Slice; +use device_id::{DeviceId, MinorId}; use id_alloc::IdAlloc; use log::{debug, info}; use ostd::{ @@ -33,37 +37,76 @@ use crate::{ }, queue::VirtQueue, transport::{ConfigManager, VirtioTransport}, + VIRTIO_BLOCK_MAJOR_ID, }; +/// The number of minor device numbers allocated for each virtio disk, +/// including the whole disk and its partitions. If a disk has more than +/// 16 partitions, then allocate a device ID via `EXTENDED_DEVICE_ID_ALLOCATOR`. +const VIRTIO_DEVICE_MINORS: u32 = 16; + +/// The number of virtio block devices, used to assign minor device numbers. +static NR_BLOCK_DEVICE: AtomicU32 = AtomicU32::new(0); + #[derive(Debug)] pub struct BlockDevice { device: Arc, /// The software staging queue. queue: BioRequestSingleQueue, + id: DeviceId, + name: String, + partitions: SpinLock>>>, + weak_self: Weak, } impl BlockDevice { + /// Returns the formatted device name. + /// + /// The device name starts at "vda". The 26th device is "vdz" and the 27th is "vdaa". + /// The last one for two lettered suffix is "vdzz" which is followed by "vdaaa". + fn formatted_device_name(mut index: u32) -> String { + const VIRTIO_DISK_PREFIX: &str = "vd"; + + let mut suffix = Vec::new(); + loop { + suffix.push((b'a' + (index % 26) as u8) as char); + index /= 26; + if index == 0 { + break; + } + index -= 1; + } + suffix.reverse(); + let mut name = String::from(VIRTIO_DISK_PREFIX); + name.extend(suffix); + name + } + /// Creates a new VirtIO-Block driver and registers it. pub(crate) fn init(transport: Box) -> Result<(), VirtioDeviceError> { - let is_legacy = transport.is_legacy_version(); let device = DeviceInner::init(transport)?; - let device_id = if is_legacy { - // FIXME: legacy device do not support `GetId` request. - "legacy_blk".to_string() - } else { - device.request_device_id() - }; - let block_device = Arc::new(Self { + let index = NR_BLOCK_DEVICE.fetch_add(1, Ordering::Relaxed); + let id = DeviceId::new( + VIRTIO_BLOCK_MAJOR_ID.get().unwrap().get(), + MinorId::new(index * VIRTIO_DEVICE_MINORS), + ); + let name = Self::formatted_device_name(index); + + let block_device = Arc::new_cyclic(|weak_self| BlockDevice { device, // Each bio request includes an additional 1 request and 1 response descriptor, // therefore this upper bound is set to (QUEUE_SIZE - 2). queue: BioRequestSingleQueue::with_max_nr_segments_per_bio( (DeviceInner::QUEUE_SIZE - 2) as usize, ), + id, + name, + partitions: SpinLock::new(None), + weak_self: weak_self.clone(), }); - aster_block::register_device(device_id, block_device); + aster_block::register(block_device).unwrap(); bio_segment_pool_init(); Ok(()) @@ -101,6 +144,58 @@ impl aster_block::BlockDevice for BlockDevice { nr_sectors: self.device.config_manager.capacity_sectors(), } } + + fn name(&self) -> &str { + &self.name + } + + fn id(&self) -> DeviceId { + self.id + } + + fn set_partitions(&self, infos: Vec>) { + let mut partitions = self.partitions.lock(); + if let Some(old_partitions) = partitions.take() { + for partition in old_partitions { + let _ = aster_block::unregister(partition.id()); + } + } + + let mut new_partitions = Vec::new(); + for (index, info_opt) in infos.iter().enumerate() { + let Some(info) = info_opt else { + continue; + }; + + let index = index as u32 + 1; + let id = if index < VIRTIO_DEVICE_MINORS { + DeviceId::new(self.id.major(), MinorId::new(self.id.minor().get() + index)) + } else { + EXTENDED_DEVICE_ID_ALLOCATOR.get().unwrap().allocate() + }; + let name = format!("{}{}", self.name(), index); + let device = self.weak_self.upgrade().unwrap(); + + let partition = Arc::new(PartitionNode::new(id, name, device, *info)); + new_partitions.push(partition); + } + + for partition in new_partitions.iter() { + let _ = aster_block::register(partition.clone()); + } + + *partitions = Some(new_partitions); + } + + fn partitions(&self) -> Option>> { + let partitions = self.partitions.lock(); + let devices = partitions + .as_ref()? + .iter() + .map(|p| p.clone() as Arc) + .collect(); + Some(devices) + } } #[derive(Debug)] @@ -240,73 +335,6 @@ impl DeviceInner { info!("Virtio block device config space change"); } - // TODO: Most logic is the same as read and write, there should be a refactor. - // TODO: Should return an Err instead of panic if the device fails. - fn request_device_id(&self) -> String { - let id = self.id_allocator.disable_irq().lock().alloc().unwrap(); - let req_slice = { - let req_slice = Slice::new(&self.block_requests, id * REQ_SIZE..(id + 1) * REQ_SIZE); - let req = BlockReq { - type_: ReqType::GetId as _, - reserved: 0, - sector: 0, - }; - req_slice.write_val(0, &req).unwrap(); - req_slice.sync().unwrap(); - req_slice - }; - - let resp_slice = { - let resp_slice = - Slice::new(&self.block_responses, id * RESP_SIZE..(id + 1) * RESP_SIZE); - resp_slice.write_val(0, &BlockResp::default()).unwrap(); - resp_slice - }; - const MAX_ID_LENGTH: usize = 20; - let device_id_stream = { - let segment = FrameAllocOptions::new() - .zeroed(false) - .alloc_segment(1) - .unwrap(); - Arc::new(DmaStream::map(segment.into(), DmaDirection::FromDevice, false).unwrap()) - }; - let device_id_slice = Slice::new(&device_id_stream, 0..MAX_ID_LENGTH); - let outputs = vec![&device_id_slice, &resp_slice]; - - let mut queue = self.queue.disable_irq().lock(); - let token = queue - .add_dma_buf(&[&req_slice], outputs.as_slice()) - .expect("add queue failed"); - if queue.should_notify() { - queue.notify(); - } - while !queue.can_pop() { - spin_loop(); - } - queue.pop_used_with_token(token).expect("pop used failed"); - - resp_slice.sync().unwrap(); - self.id_allocator.disable_irq().lock().free(id); - let resp: BlockResp = resp_slice.read_val(0).unwrap(); - match RespStatus::try_from(resp.status).unwrap() { - RespStatus::Ok => {} - _ => panic!("io error in block device"), - }; - - let device_id = { - device_id_slice.sync().unwrap(); - let mut device_id = vec![0u8; MAX_ID_LENGTH]; - let _ = device_id_slice.read_bytes(0, &mut device_id); - let len = device_id - .iter() - .position(|&b| b == 0) - .unwrap_or(MAX_ID_LENGTH); - device_id.truncate(len); - device_id - }; - String::from_utf8(device_id).unwrap() - } - /// Reads data from the device, this function is non-blocking. fn read(&self, bio_request: BioRequest) { let id = self.id_allocator.disable_irq().lock().alloc().unwrap(); diff --git a/kernel/comps/virtio/src/lib.rs b/kernel/comps/virtio/src/lib.rs index 057b37b91..99c845581 100644 --- a/kernel/comps/virtio/src/lib.rs +++ b/kernel/comps/virtio/src/lib.rs @@ -11,6 +11,7 @@ extern crate alloc; use alloc::boxed::Box; use core::hint::spin_loop; +use aster_block::MajorIdOwner; use bitflags::bitflags; use component::{init_component, ComponentInitError}; use device::{ @@ -22,6 +23,7 @@ use device::{ VirtioDeviceType, }; use log::{error, warn}; +use spin::Once; use transport::{mmio::VIRTIO_MMIO_DRIVER, pci::VIRTIO_PCI_DRIVER, DeviceStatus}; use crate::transport::VirtioTransport; @@ -31,8 +33,12 @@ mod dma_buf; pub mod queue; mod transport; +static VIRTIO_BLOCK_MAJOR_ID: Once = Once::new(); + #[init_component] fn virtio_component_init() -> Result<(), ComponentInitError> { + VIRTIO_BLOCK_MAJOR_ID.call_once(|| aster_block::allocate_major().unwrap()); + // Find all devices and register them to the corresponding crate transport::init(); // For vsock table static init diff --git a/kernel/comps/virtio/src/transport/pci/driver.rs b/kernel/comps/virtio/src/transport/pci/driver.rs index 10d0fbe78..df87a933b 100644 --- a/kernel/comps/virtio/src/transport/pci/driver.rs +++ b/kernel/comps/virtio/src/transport/pci/driver.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use alloc::{boxed::Box, collections::vec_deque::VecDeque, sync::Arc}; use aster_pci::{ bus::{PciDevice, PciDriver}, @@ -17,17 +17,17 @@ use crate::transport::{ #[derive(Debug)] pub struct VirtioPciDriver { - devices: SpinLock>>, + devices: SpinLock>>, } impl VirtioPciDriver { pub fn pop_device_transport(&self) -> Option> { - self.devices.lock().pop() + self.devices.lock().pop_front() } pub(super) fn new() -> Self { VirtioPciDriver { - devices: SpinLock::new(Vec::new()), + devices: SpinLock::new(VecDeque::new()), } } } @@ -66,7 +66,7 @@ impl PciDriver for VirtioPciDriver { } _ => return Err((BusProbeError::DeviceNotMatch, device)), }; - self.devices.lock().push(transport); + self.devices.lock().push_back(transport); Ok(Arc::new(VirtioPciDevice::new(device_id))) } diff --git a/kernel/libs/device-id/Cargo.toml b/kernel/libs/device-id/Cargo.toml index 38697ac29..fe4efa8e7 100644 --- a/kernel/libs/device-id/Cargo.toml +++ b/kernel/libs/device-id/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +aster-util = { path = "../aster-util" } [lints] workspace = true diff --git a/kernel/libs/device-id/src/lib.rs b/kernel/libs/device-id/src/lib.rs index 35496c767..92c67cd9f 100644 --- a/kernel/libs/device-id/src/lib.rs +++ b/kernel/libs/device-id/src/lib.rs @@ -12,59 +12,92 @@ #![no_std] #![deny(unsafe_code)] -/// A device ID, containing a major device number and a minor device number. +use aster_util::ranged_integer::{RangedU16, RangedU32}; + +/// A device ID, embedding the major ID and minor ID. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct DeviceId { - major: u32, - minor: u32, -} +pub struct DeviceId(u32); impl DeviceId { /// Creates a device ID from the major device number and the minor device number. - pub fn new(major: u32, minor: u32) -> Self { - Self { major, minor } + pub fn new(major: MajorId, minor: MinorId) -> Self { + Self(((major.get() as u32) << 20) | minor.get()) + } + + /// Returns the encoded `u32` value. + pub fn to_raw(&self) -> u32 { + self.0 } /// Returns the major device number. - pub fn major(&self) -> u32 { - self.major + pub fn major(&self) -> MajorId { + MajorId::new((self.0 >> 20) as u16) } /// Returns the minor device number. - pub fn minor(&self) -> u32 { - self.minor + pub fn minor(&self) -> MinorId { + MinorId::new(self.0 & 0xf_ffff) } } impl DeviceId { /// Creates a device ID from the encoded `u64` value. /// - /// See [`as_encoded_u64`] for details about how to encode a device ID to a `u64` value. - /// - /// [`as_encoded_u64`]: Self::as_encoded_u64 + /// Panics if the major or minor device number is not falling in the valid range. pub fn from_encoded_u64(raw: u64) -> Self { - let major = ((raw >> 32) & 0xffff_f000 | (raw >> 8) & 0x0000_0fff) as u32; - let minor = ((raw >> 12) & 0xffff_ff00 | raw & 0x0000_00ff) as u32; - Self::new(major, minor) + let (major, minor) = decode_device_numbers(raw); + Self::new(MajorId::new(major as u16), MinorId::new(minor)) } /// Encodes the device ID as a `u64` value. - /// - /// The lower 32 bits use the same encoding strategy as Linux. See the Linux implementation at: - /// . - /// - /// If the major or minor device number is too large, the additional bits will be recorded - /// using the higher 32 bits. Note that as of 2025, the Linux kernel still has no support for - /// 64-bit device IDs: - /// . - /// So this encoding follows the implementation in glibc: - /// . pub fn as_encoded_u64(&self) -> u64 { - let major = self.major() as u64; - let minor = self.minor() as u64; - ((major & 0xffff_f000) << 32) - | ((major & 0x0000_0fff) << 8) - | ((minor & 0xffff_ff00) << 12) - | (minor & 0x0000_00ff) + encode_device_numbers(self.major().get() as u32, self.minor().get()) } } + +/// Decodes the major and minor numbers from the encoded `u64` value. +/// +/// See [`DeviceId::as_encoded_u64`] for details about how to encode a device ID to a `u64` value. +pub fn decode_device_numbers(raw: u64) -> (u32, u32) { + let major = ((raw >> 32) & 0xffff_f000 | (raw >> 8) & 0x0000_0fff) as u32; + let minor = ((raw >> 12) & 0xffff_ff00 | raw & 0x0000_00ff) as u32; + (major, minor) +} + +/// Encodes the major and minor numbers as a `u64` value. +/// +/// The lower 32 bits use the same encoding strategy as Linux. See the Linux implementation at: +/// . +/// +/// If the major or minor device number is too large, the additional bits will be recorded +/// using the higher 32 bits. Note that as of 2025, the Linux kernel still has no support for +/// 64-bit device IDs: +/// . +/// So this encoding follows the implementation in glibc: +/// . +pub fn encode_device_numbers(major: u32, minor: u32) -> u64 { + let major = major as u64; + let minor = minor as u64; + ((major & 0xffff_f000) << 32) + | ((major & 0x0000_0fff) << 8) + | ((minor & 0xffff_ff00) << 12) + | (minor & 0x0000_00ff) +} + +const MAX_MAJOR_ID: u16 = 0x0fff; + +const MAX_MINOR_ID: u32 = 0x000f_ffff; + +/// The major component of a device ID. +/// +/// A major ID is a non-zero, 12-bit integer, thus falling in the range of `1..(1u16 << 12)`. +/// +/// Reference: . +pub type MajorId = RangedU16<1, MAX_MAJOR_ID>; + +/// The minor component of a device ID. +/// +/// A minor ID is a 20-bit integer, thus falling in the range of `0..(1u32 << 20)`. +/// +/// Reference: . +pub type MinorId = RangedU32<0, MAX_MINOR_ID>; diff --git a/kernel/src/device/disk.rs b/kernel/src/device/disk.rs new file mode 100644 index 000000000..b6029378d --- /dev/null +++ b/kernel/src/device/disk.rs @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MPL-2.0 + +use aster_block::BlockDevice; +use aster_virtio::device::block::device::BlockDevice as VirtIoBlockDevice; +use device_id::DeviceId; +use ostd::mm::VmIo; + +use crate::{ + events::IoEvents, + fs::{ + device::{add_node, Device, DeviceType}, + fs_resolver::FsResolver, + inode_handle::FileIo, + utils::{InodeIo, StatusFlags}, + }, + prelude::*, + process::signal::{PollHandle, Pollable}, + thread::kernel_thread::ThreadOptions, +}; + +pub(super) fn init_in_first_kthread() { + for device in aster_block::collect_all() { + if device.is_partition() { + continue; + } + + let task_fn = move || { + info!("spawn the virt-io-block thread"); + let virtio_block_device = device.downcast_ref::().unwrap(); + loop { + virtio_block_device.handle_requests(); + } + }; + ThreadOptions::new(task_fn).spawn(); + } +} + +pub(super) fn init_in_first_process(fs_resolver: &FsResolver) -> Result<()> { + for device in aster_block::collect_all() { + let name = device.name().to_string(); + let device = Arc::new(BlockFile::new(device)); + add_node(device, &name, fs_resolver)?; + } + + Ok(()) +} + +#[derive(Debug)] +struct BlockFile(Arc); + +impl BlockFile { + fn new(device: Arc) -> Self { + Self(device) + } +} + +impl InodeIo for BlockFile { + fn read_at( + &self, + offset: usize, + writer: &mut VmWriter, + _status_flags: StatusFlags, + ) -> Result { + let total = writer.avail(); + self.0.read(offset, writer)?; + let avail = writer.avail(); + Ok(total - avail) + } + + fn write_at( + &self, + offset: usize, + reader: &mut VmReader, + _status_flags: StatusFlags, + ) -> Result { + let total = reader.remain(); + self.0.write(offset, reader)?; + let remain = reader.remain(); + Ok(total - remain) + } +} + +impl Pollable for BlockFile { + fn poll(&self, mask: IoEvents, _: Option<&mut PollHandle>) -> IoEvents { + let events = IoEvents::IN | IoEvents::OUT; + events & mask + } +} + +impl FileIo for BlockFile { + fn check_seekable(&self) -> Result<()> { + Ok(()) + } + + fn is_offset_aware(&self) -> bool { + true + } +} + +impl Device for BlockFile { + fn type_(&self) -> DeviceType { + DeviceType::Block + } + + fn id(&self) -> DeviceId { + self.0.id() + } + + fn open(&self) -> Result> { + Ok(Box::new(BlockFile(self.0.clone()))) + } +} diff --git a/kernel/src/device/full.rs b/kernel/src/device/full.rs index f0d2ec7a5..3347cf242 100644 --- a/kernel/src/device/full.rs +++ b/kernel/src/device/full.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use crate::{ events::IoEvents, @@ -22,7 +22,7 @@ impl Device for Full { fn id(&self) -> DeviceId { // The same value as Linux - DeviceId::new(1, 7) + DeviceId::new(MajorId::new(1), MinorId::new(7)) } fn open(&self) -> Result> { diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index eac235902..423d930ea 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 +mod disk; mod full; mod null; mod pty; @@ -29,6 +30,10 @@ use crate::{ prelude::*, }; +pub fn init_in_first_kthread() { + disk::init_in_first_kthread(); +} + /// Init the device node in fs, must be called after mounting rootfs. pub fn init_in_first_process(ctx: &Context) -> Result<()> { let fs = ctx.thread_local.borrow_fs(); @@ -74,6 +79,8 @@ pub fn init_in_first_process(ctx: &Context) -> Result<()> { shm::init_in_first_process(&fs_resolver, ctx)?; + disk::init_in_first_process(&fs_resolver)?; + Ok(()) } @@ -82,8 +89,8 @@ pub fn init_in_first_process(ctx: &Context) -> Result<()> { // a registration mechanism should be used to allow each driver to // allocate device IDs either statically or dynamically. pub fn get_device(devid: DeviceId) -> Result> { - let major = devid.major(); - let minor = devid.minor(); + let major = devid.major().get(); + let minor = devid.minor().get(); match (major, minor) { (1, 3) => Ok(Arc::new(null::Null)), diff --git a/kernel/src/device/null.rs b/kernel/src/device/null.rs index 1d0c66244..e04159f21 100644 --- a/kernel/src/device/null.rs +++ b/kernel/src/device/null.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use crate::{ events::IoEvents, @@ -22,7 +22,7 @@ impl Device for Null { fn id(&self) -> DeviceId { // The same value as Linux - DeviceId::new(1, 3) + DeviceId::new(MajorId::new(1), MinorId::new(3)) } fn open(&self) -> Result> { diff --git a/kernel/src/device/random.rs b/kernel/src/device/random.rs index 1a52e870e..be1e69933 100644 --- a/kernel/src/device/random.rs +++ b/kernel/src/device/random.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use super::Urandom; use crate::{ @@ -30,7 +30,7 @@ impl Device for Random { fn id(&self) -> DeviceId { // The same value as Linux - DeviceId::new(1, 8) + DeviceId::new(MajorId::new(1), MinorId::new(8)) } fn open(&self) -> Result> { diff --git a/kernel/src/device/tdxguest/mod.rs b/kernel/src/device/tdxguest/mod.rs index 2a9282d37..b9cdf9303 100644 --- a/kernel/src/device/tdxguest/mod.rs +++ b/kernel/src/device/tdxguest/mod.rs @@ -6,7 +6,7 @@ use core::{ }; use aster_util::{field_ptr, safe_ptr::SafePtr}; -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use ostd::{ const_assert, mm::{ @@ -40,7 +40,7 @@ impl Device for TdxGuest { } fn id(&self) -> DeviceId { - DeviceId::new(0xa, 0x7b) + DeviceId::new(MajorId::new(0xa), MinorId::new(0x7b)) } fn open(&self) -> Result> { diff --git a/kernel/src/device/tty/device.rs b/kernel/src/device/tty/device.rs index 67fa4eeda..7164c43d3 100644 --- a/kernel/src/device/tty/device.rs +++ b/kernel/src/device/tty/device.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use crate::{ fs::{ @@ -31,6 +31,6 @@ impl Device for TtyDevice { } fn id(&self) -> DeviceId { - DeviceId::new(5, 0) + DeviceId::new(MajorId::new(5), MinorId::new(0)) } } diff --git a/kernel/src/device/tty/mod.rs b/kernel/src/device/tty/mod.rs index 2ccf50b16..89b7090be 100644 --- a/kernel/src/device/tty/mod.rs +++ b/kernel/src/device/tty/mod.rs @@ -5,7 +5,7 @@ use aster_console::{ mode::{ConsoleMode, KeyboardMode}, AnyConsoleDevice, }; -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use ostd::sync::LocalIrqDisabled; use self::{line_discipline::LineDiscipline, termio::CFontOp}; @@ -401,7 +401,10 @@ impl Device for Tty { } fn id(&self) -> DeviceId { - DeviceId::new(D::DEVICE_MAJOR_ID, self.index) + DeviceId::new( + MajorId::new(D::DEVICE_MAJOR_ID as u16), + MinorId::new(self.index), + ) } fn open(&self) -> Result> { diff --git a/kernel/src/device/urandom.rs b/kernel/src/device/urandom.rs index 190f3e00a..013a90edf 100644 --- a/kernel/src/device/urandom.rs +++ b/kernel/src/device/urandom.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use crate::{ events::IoEvents, @@ -47,7 +47,7 @@ impl Device for Urandom { fn id(&self) -> DeviceId { // The same value as Linux - DeviceId::new(1, 9) + DeviceId::new(MajorId::new(1), MinorId::new(9)) } fn open(&self) -> Result> { diff --git a/kernel/src/device/zero.rs b/kernel/src/device/zero.rs index 826d228d0..94d0d5804 100644 --- a/kernel/src/device/zero.rs +++ b/kernel/src/device/zero.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use crate::{ events::IoEvents, @@ -22,7 +22,7 @@ impl Device for Zero { fn id(&self) -> DeviceId { // The same value as Linux - DeviceId::new(1, 5) + DeviceId::new(MajorId::new(1), MinorId::new(5)) } fn open(&self) -> Result> { diff --git a/kernel/src/fs/device.rs b/kernel/src/fs/device.rs index 9b5298ca0..3b57ff6c6 100644 --- a/kernel/src/fs/device.rs +++ b/kernel/src/fs/device.rs @@ -37,7 +37,6 @@ impl Debug for dyn Device { #[derive(Debug)] pub enum DeviceType { Char, - #[expect(dead_code)] Block, #[expect(dead_code)] Misc, diff --git a/kernel/src/fs/devpts/ptmx.rs b/kernel/src/fs/devpts/ptmx.rs index 43560a20b..d85fd671e 100644 --- a/kernel/src/fs/devpts/ptmx.rs +++ b/kernel/src/fs/devpts/ptmx.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use device_id::DeviceId; +use device_id::{DeviceId, MajorId, MinorId}; use super::*; use crate::fs::{ @@ -9,7 +9,7 @@ use crate::fs::{ }; /// Same major number with Linux. -const PTMX_MAJOR_NUM: u32 = 5; +const PTMX_MAJOR_NUM: u16 = 5; /// Same minor number with Linux. const PTMX_MINOR_NUM: u32 = 2; @@ -162,7 +162,7 @@ impl Device for Inner { } fn id(&self) -> DeviceId { - DeviceId::new(PTMX_MAJOR_NUM, PTMX_MINOR_NUM) + DeviceId::new(MajorId::new(PTMX_MAJOR_NUM), MinorId::new(PTMX_MINOR_NUM)) } fn open(&self) -> Result> { diff --git a/kernel/src/fs/exfat/mod.rs b/kernel/src/fs/exfat/mod.rs index 7c5c1a3e5..7a542a480 100644 --- a/kernel/src/fs/exfat/mod.rs +++ b/kernel/src/fs/exfat/mod.rs @@ -10,8 +10,6 @@ mod super_block; mod upcase_table; mod utils; -pub use fs::{ExfatFs, ExfatMountOptions}; - use crate::fs::exfat::fs::ExfatType; pub(super) fn init() { @@ -26,18 +24,17 @@ mod test { bio::{BioEnqueueError, BioStatus, BioType, SubmittedBio}, BlockDevice, BlockDeviceMeta, }; + use device_id::DeviceId; use ostd::{ mm::{io_util::HasVmReaderWriter, FrameAllocOptions, Segment, VmIo, PAGE_SIZE}, prelude::*, }; use rand::{rngs::SmallRng, RngCore, SeedableRng}; + use super::fs::{ExfatFs, ExfatMountOptions}; use crate::{ fs::{ - exfat::{ - constants::{EXFAT_RESERVED_CLUSTERS, MAX_NAME_LENGTH}, - ExfatFs, ExfatMountOptions, - }, + exfat::constants::{EXFAT_RESERVED_CLUSTERS, MAX_NAME_LENGTH}, utils::{generate_random_operation, new_fs_in_memory, Inode, InodeMode, InodeType}, }, prelude::*, @@ -111,6 +108,14 @@ mod test { nr_sectors: self.sectors_count(), } } + + fn name(&self) -> &str { + todo!() + } + + fn id(&self) -> DeviceId { + todo!() + } } /// Exfat disk image static EXFAT_IMAGE: &[u8] = include_bytes!("../../../../test/build/exfat.img"); diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs index 3149a4279..0aa41d6b5 100644 --- a/kernel/src/fs/mod.rs +++ b/kernel/src/fs/mod.rs @@ -24,38 +24,15 @@ pub mod thread_info; pub mod tmpfs; pub mod utils; -use aster_block::BlockDevice; -use aster_virtio::device::block::device::BlockDevice as VirtIoBlockDevice; - use crate::{ fs::{ - exfat::{ExfatFs, ExfatMountOptions}, - ext2::Ext2, file_table::FdFlags, fs_resolver::{FsPath, FsResolver}, utils::{mkmod, AccessMode, OpenArgs}, }, prelude::*, - thread::kernel_thread::ThreadOptions, }; -fn start_block_device(device_name: &str) -> Result> { - if let Some(device) = aster_block::get_device(device_name) { - let cloned_device = device.clone(); - let task_fn = move || { - info!("spawn the virt-io-block thread"); - let virtio_block_device = cloned_device.downcast_ref::().unwrap(); - loop { - virtio_block_device.handle_requests(); - } - }; - ThreadOptions::new(task_fn).spawn(); - Ok(device) - } else { - return_errno_with_message!(Errno::ENOENT, "Device does not exist") - } -} - pub fn init() { registry::init(); @@ -84,27 +61,9 @@ pub fn init_in_first_kthread(fs_resolver: &FsResolver) { } pub fn init_in_first_process(ctx: &Context) { - //The device name is specified in qemu args as --serial={device_name} - let ext2_device_name = "vext2"; - let exfat_device_name = "vexfat"; - let fs = ctx.thread_local.borrow_fs(); let fs_resolver = fs.resolver().read(); - if let Ok(block_device_ext2) = start_block_device(ext2_device_name) { - let ext2_fs = Ext2::open(block_device_ext2).unwrap(); - let target_path = FsPath::try_from("/ext2").unwrap(); - println!("[kernel] Mount Ext2 fs at {:?} ", target_path); - self::rootfs::mount_fs_at(ext2_fs, &target_path, &fs_resolver, ctx).unwrap(); - } - - if let Ok(block_device_exfat) = start_block_device(exfat_device_name) { - let exfat_fs = ExfatFs::open(block_device_exfat, ExfatMountOptions::default()).unwrap(); - let target_path = FsPath::try_from("/exfat").unwrap(); - println!("[kernel] Mount ExFat fs at {:?} ", target_path); - self::rootfs::mount_fs_at(exfat_fs, &target_path, &fs_resolver, ctx).unwrap(); - } - // Initialize the file table for the first process. let tty_path = FsPath::try_from("/dev/console").unwrap(); let stdin = { diff --git a/kernel/src/fs/rootfs.rs b/kernel/src/fs/rootfs.rs index 5a6d67c2f..769616a46 100644 --- a/kernel/src/fs/rootfs.rs +++ b/kernel/src/fs/rootfs.rs @@ -8,12 +8,9 @@ use ostd::boot::boot_info; use super::{ fs_resolver::{FsPath, FsResolver}, - utils::{FileSystem, InodeMode, InodeType}, -}; -use crate::{ - fs::path::{is_dot, PerMountFlags}, - prelude::*, + utils::{InodeMode, InodeType}, }; +use crate::{fs::path::is_dot, prelude::*}; struct BoxedReader<'a>(Box); @@ -109,14 +106,3 @@ pub fn init_in_first_kthread(fs_resolver: &FsResolver) -> Result<()> { println!("[kernel] rootfs is ready"); Ok(()) } - -pub fn mount_fs_at( - fs: Arc, - fs_path: &FsPath, - fs_resolver: &FsResolver, - ctx: &Context, -) -> Result<()> { - let target_path = fs_resolver.lookup(fs_path)?; - target_path.mount(fs, PerMountFlags::default(), ctx)?; - Ok(()) -} diff --git a/kernel/src/fs/utils/inode.rs b/kernel/src/fs/utils/inode.rs index 20c880202..a46878e69 100644 --- a/kernel/src/fs/utils/inode.rs +++ b/kernel/src/fs/utils/inode.rs @@ -47,7 +47,6 @@ impl InodeType { *self == InodeType::Dir } - #[expect(dead_code)] pub fn is_device(&self) -> bool { *self == InodeType::BlockDevice || *self == InodeType::CharDevice } diff --git a/kernel/src/init.rs b/kernel/src/init.rs index 41043bd0e..694278b2f 100644 --- a/kernel/src/init.rs +++ b/kernel/src/init.rs @@ -162,6 +162,7 @@ fn init_in_first_kthread(fs_resolver: &FsResolver) { // Work queue should be initialized before interrupt is enabled, // in case any irq handler uses work queue as bottom half crate::thread::work_queue::init_in_first_kthread(); + crate::device::init_in_first_kthread(); crate::net::init_in_first_kthread(); crate::fs::init_in_first_kthread(fs_resolver); crate::ipc::init_in_first_kthread(); diff --git a/kernel/src/syscall/mount.rs b/kernel/src/syscall/mount.rs index 936d19178..83dec758a 100644 --- a/kernel/src/syscall/mount.rs +++ b/kernel/src/syscall/mount.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use device_id::DeviceId; + use super::SyscallReturn; use crate::{ fs::{ @@ -227,10 +229,25 @@ fn get_fs( let disk = if fs_type.properties().contains(FsProperties::NEED_DISK) { let devname = user_space.read_cstring(src_name_addr, MAX_FILENAME_LEN)?; - Some( - aster_block::get_device(devname.to_str().unwrap()) - .ok_or(Error::with_message(Errno::ENOENT, "device does not exist"))?, - ) + let path = devname.to_string_lossy(); + let fs_path = FsPath::from_fd_and_path(AT_FDCWD, path.as_ref())?; + let path = ctx + .thread_local + .borrow_fs() + .resolver() + .read() + .lookup_no_follow(&fs_path)?; + if !path.type_().is_device() { + return_errno_with_message!(Errno::ENODEV, "the path is not a device file"); + } + + let id = DeviceId::from_encoded_u64(path.metadata().rdev); + let device = aster_block::lookup(id); + if device.is_none() { + return_errno_with_message!(Errno::ENODEV, "the device is not found"); + } + + device } else { None }; diff --git a/kernel/src/syscall/statx.rs b/kernel/src/syscall/statx.rs index e7cefeca1..5d9535160 100644 --- a/kernel/src/syscall/statx.rs +++ b/kernel/src/syscall/statx.rs @@ -2,8 +2,6 @@ use core::time::Duration; -use device_id::DeviceId; - use super::SyscallReturn; use crate::{ fs::{file_table::FileDesc, fs_resolver::FsPath, utils::Metadata}, @@ -123,8 +121,8 @@ pub struct Statx { impl From for Statx { fn from(info: Metadata) -> Self { - let devid = DeviceId::from_encoded_u64(info.dev); - let rdevid = DeviceId::from_encoded_u64(info.rdev); + let (stx_dev_major, stx_dev_minor) = device_id::decode_device_numbers(info.dev); + let (stx_rdev_major, stx_rdev_minor) = device_id::decode_device_numbers(info.rdev); // FIXME: We assume it is always not mount_root. let stx_attributes = 0; @@ -162,10 +160,10 @@ impl From for Statx { stx_btime: StatxTimestamp::from(info.atime), stx_ctime: StatxTimestamp::from(info.ctime), stx_mtime: StatxTimestamp::from(info.ctime), - stx_rdev_major: rdevid.major(), - stx_rdev_minor: rdevid.minor(), - stx_dev_major: devid.major(), - stx_dev_minor: devid.minor(), + stx_rdev_major, + stx_rdev_minor, + stx_dev_major, + stx_dev_minor, stx_mnt_id: 0, stx_dio_mem_align: 0, stx_dio_offset_align: 0, diff --git a/test/src/etc/profile.d/init.sh b/test/src/etc/profile.d/init.sh index 9ed2c2da6..b964c646d 100644 --- a/test/src/etc/profile.d/init.sh +++ b/test/src/etc/profile.d/init.sh @@ -7,4 +7,6 @@ mount -t sysfs none /sys mount -t proc none /proc mount -t cgroup2 none /sys/fs/cgroup -mount -t configfs none /sys/kernel/config \ No newline at end of file +mount -t configfs none /sys/kernel/config +mount -t ext2 /dev/vda /ext2 +mount -t exfat /dev/vdb /exfat \ No newline at end of file