Add partition support for block device

This commit is contained in:
Qingsong Chen 2025-11-19 08:20:13 +00:00 committed by Tate, Hongliang Tian
parent d954e3d006
commit 9e2f5adf9b
37 changed files with 940 additions and 277 deletions

7
Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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"] }

View File

@ -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<Sid>,
/// 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<BioSegment>,
/// The I/O completion method

View File

@ -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: <https://elixir.bootlin.com/linux/v6.13/source/block/genhd.c#L239>.
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: <https://elixir.bootlin.com/linux/v6.13/source/block/genhd.c#L224>.
const LAST_DYNAMIC_MAJOR: u16 = 254;
static MAJORS: Mutex<BTreeSet<u16>> = 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<MajorIdOwner, Error> {
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<MajorIdOwner, Error> {
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: <https://elixir.bootlin.com/linux/v6.13/source/block/partitions/core.c#L352>.
const EXTENDED_MAJOR: u16 = 259;
/// An allocator for extended device IDs.
pub struct ExtendedDeviceIdAllocator {
major: MajorIdOwner,
minor_allocator: Mutex<IdAlloc>,
}
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<ExtendedDeviceIdAllocator> = Once::new();
pub(super) fn init() {
EXTENDED_DEVICE_ID_ALLOCATOR.call_once(ExtendedDeviceIdAllocator::new);
}

View File

@ -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<Option<PartitionInfo>>) {}
/// Returns the partitions of the block device.
fn partitions(&self) -> Option<Vec<Arc<dyn BlockDevice>>> {
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<dyn BlockDevice>) {
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<Arc<dyn BlockDevice>> {
COMPONENT
.get()
.unwrap()
.block_device_table
.lock()
.get(str)
.cloned()
}
/// Registers a new block device.
pub fn register(device: Arc<dyn BlockDevice>) -> 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<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>>>,
/// Unregisters an existing block device, returning the device if found.
pub fn unregister(id: DeviceId) -> Result<Arc<dyn BlockDevice>, Error> {
DEVICE_REGISTRY
.lock()
.remove(&id.to_raw())
.ok_or(Error::NotFound)
}
impl Component {
pub fn init() -> Result<Self, ComponentInitError> {
Ok(Self {
block_device_table: SpinLock::new(BTreeMap::new()),
})
}
/// Collects all block devices.
pub fn collect_all() -> Vec<Arc<dyn BlockDevice>> {
DEVICE_REGISTRY.lock().values().cloned().collect()
}
/// Looks up a block device of a given device ID.
pub fn lookup(id: DeviceId) -> Option<Arc<dyn BlockDevice>> {
DEVICE_REGISTRY.lock().get(&id.to_raw()).cloned()
}
static DEVICE_REGISTRY: Mutex<BTreeMap<u32, Arc<dyn BlockDevice>>> = 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(())
}

View File

@ -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 <https://wiki.osdev.org/MBR_(x86)#MBR_Format>.
#[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 <https://wiki.osdev.org/Partition_Table>.
#[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 05 are the sector number (6 bits, valid values 163);
/// bits 67 are the upper two bits of the cylinder number
/// - Byte 2: Lower 8 bits of the cylinder number (bits 07)
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Pod)]
struct ChsAddr([u8; 3]);
/// A GPT (GUID Partition Table) header.
///
/// See <https://wiki.osdev.org/GPT#LBA_1:_Partition_Table_Header>.
#[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 <https://wiki.osdev.org/GPT#LBA_2:_Partition_Entries>.
#[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<dyn BlockDevice>) -> Option<Vec<Option<PartitionInfo>>> {
let mbr = device.read_val::<MbrHeader>(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<dyn BlockDevice>, mbr: &MbrHeader) -> Vec<Option<PartitionInfo>> {
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<dyn BlockDevice>,
partitions: &mut Vec<Option<PartitionInfo>>,
start_sector: u32,
offset: u32,
) {
let ebr_sector = start_sector + offset;
let mut ebr = device
.read_val::<MbrHeader>(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<dyn BlockDevice>) -> Vec<Option<PartitionInfo>> {
let mut partitions = Vec::new();
// The primary GPT Header must be located in LBA 1.
let gpt = device.read_val::<GptHeader>(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<dyn BlockDevice>,
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<dyn BlockDevice>,
info: PartitionInfo,
) -> Self {
Self {
id,
name,
device,
info,
}
}
}

View File

@ -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<Sid>,
/// 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<SubmittedBio> 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);

View File

@ -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.

View File

@ -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<D: BlockSet + 'static> aster_block::BlockDevice for MlsDisk<D> {
nr_sectors: (BLOCK_SIZE / SECTOR_SIZE) * self.total_blocks(),
}
}
fn name(&self) -> &str {
todo!()
}
fn id(&self) -> DeviceId {
todo!()
}
}
impl<D: BlockSet + 'static> MlsDisk<D> {

View File

@ -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 {

View File

@ -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" }

View File

@ -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<DeviceInner>,
/// The software staging queue.
queue: BioRequestSingleQueue,
id: DeviceId,
name: String,
partitions: SpinLock<Option<Vec<Arc<PartitionNode>>>>,
weak_self: Weak<Self>,
}
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<dyn VirtioTransport>) -> 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<Option<PartitionInfo>>) {
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<Vec<Arc<dyn aster_block::BlockDevice>>> {
let partitions = self.partitions.lock();
let devices = partitions
.as_ref()?
.iter()
.map(|p| p.clone() as Arc<dyn aster_block::BlockDevice>)
.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();

View File

@ -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<MajorIdOwner> = 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

View File

@ -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<Vec<Box<dyn VirtioTransport>>>,
devices: SpinLock<VecDeque<Box<dyn VirtioTransport>>>,
}
impl VirtioPciDriver {
pub fn pop_device_transport(&self) -> Option<Box<dyn VirtioTransport>> {
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)))
}

View File

@ -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

View File

@ -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:
/// <https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/include/linux/kdev_t.h#L39-L44>.
///
/// 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:
/// <https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/include/linux/types.h#L18>.
/// So this encoding follows the implementation in glibc:
/// <https://github.com/bminor/glibc/blob/632d895f3e5d98162f77b9c3c1da4ec19968b671/bits/sysmacros.h#L26-L34>.
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:
/// <https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/include/linux/kdev_t.h#L39-L44>.
///
/// 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:
/// <https://github.com/torvalds/linux/blob/0ff41df1cb268fc69e703a08a57ee14ae967d0ca/include/linux/types.h#L18>.
/// So this encoding follows the implementation in glibc:
/// <https://github.com/bminor/glibc/blob/632d895f3e5d98162f77b9c3c1da4ec19968b671/bits/sysmacros.h#L26-L34>.
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: <https://elixir.bootlin.com/linux/v6.13/source/include/linux/kdev_t.h#L10>.
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: <https://elixir.bootlin.com/linux/v6.13/source/include/linux/kdev_t.h#L11>.
pub type MinorId = RangedU32<0, MAX_MINOR_ID>;

112
kernel/src/device/disk.rs Normal file
View File

@ -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::<VirtIoBlockDevice>().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<dyn BlockDevice>);
impl BlockFile {
fn new(device: Arc<dyn BlockDevice>) -> Self {
Self(device)
}
}
impl InodeIo for BlockFile {
fn read_at(
&self,
offset: usize,
writer: &mut VmWriter,
_status_flags: StatusFlags,
) -> Result<usize> {
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<usize> {
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<Box<dyn FileIo>> {
Ok(Box::new(BlockFile(self.0.clone())))
}
}

View File

@ -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<Box<dyn FileIo>> {

View File

@ -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<Arc<dyn Device>> {
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)),

View File

@ -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<Box<dyn FileIo>> {

View File

@ -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<Box<dyn FileIo>> {

View File

@ -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<Box<dyn FileIo>> {

View File

@ -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))
}
}

View File

@ -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<D: TtyDriver> Device for Tty<D> {
}
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<Box<dyn FileIo>> {

View File

@ -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<Box<dyn FileIo>> {

View File

@ -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<Box<dyn FileIo>> {

View File

@ -37,7 +37,6 @@ impl Debug for dyn Device {
#[derive(Debug)]
pub enum DeviceType {
Char,
#[expect(dead_code)]
Block,
#[expect(dead_code)]
Misc,

View File

@ -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<Box<dyn FileIo>> {

View File

@ -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");

View File

@ -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<Arc<dyn BlockDevice>> {
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::<VirtIoBlockDevice>().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 = {

View File

@ -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<dyn Read + 'a>);
@ -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<dyn FileSystem>,
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(())
}

View File

@ -47,7 +47,6 @@ impl InodeType {
*self == InodeType::Dir
}
#[expect(dead_code)]
pub fn is_device(&self) -> bool {
*self == InodeType::BlockDevice || *self == InodeType::CharDevice
}

View File

@ -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();

View File

@ -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
};

View File

@ -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<Metadata> 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<Metadata> 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,

View File

@ -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
mount -t configfs none /sys/kernel/config
mount -t ext2 /dev/vda /ext2
mount -t exfat /dev/vdb /exfat