RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / block / mod.rs
5467 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Block devices: things that store fixed-size *sectors* (512 bytes) you can
//! read and write by number (LBA = logical block address). Disks, partitions
//! of disks, and a RAM disk for tests all implement [`BlockDevice`], so file
//! systems don't care which one they sit on.
//!
//! Layers, bottom to top:
//!   virtio_blk (a QEMU disk)  ->  partition (a slice of a disk)  ->  fs::fat (FAT32)
//!
//! Devices are registered by name ("vda", "vda1", like Linux) in a global
//! list, see [`register`] and [`devices`].

pub mod partition;
pub mod virtio_blk;

use crate::errno::Errno;
use alloc::{string::String, sync::Arc, vec, vec::Vec};
use spin::Mutex;

pub const SECTOR_SIZE: usize = 512;

pub trait BlockDevice: Send + Sync {
    /// Number of sectors.
    fn sector_count(&self) -> u64;
    /// Read `buf.len() / 512` sectors starting at `lba` (buf.len() must be a
    /// multiple of 512).
    fn read_sectors(&self, lba: u64, buf: &mut [u8]) -> Result<(), Errno>;
    /// Write `buf.len() / 512` sectors starting at `lba`.
    fn write_sectors(&self, lba: u64, buf: &[u8]) -> Result<(), Errno>;
}

/// Bounds check shared by all devices: whole sectors, inside the device.
pub fn check_range(dev: &dyn BlockDevice, lba: u64, len: usize) -> Result<u64, Errno> {
    if len % SECTOR_SIZE != 0 {
        return Err(Errno::EINVAL);
    }
    let count = (len / SECTOR_SIZE) as u64;
    match lba.checked_add(count) {
        Some(end) if end <= dev.sector_count() => Ok(count),
        _ => Err(Errno::EIO), // like reading past the end of a real disk
    }
}

/// A disk in RAM (for tests, and handy for experiments).
pub struct RamDisk {
    data: Mutex<Vec<u8>>,
}

impl RamDisk {
    pub fn new(sectors: usize) -> Self {
        RamDisk { data: Mutex::new(vec![0; sectors * SECTOR_SIZE]) }
    }
}

impl BlockDevice for RamDisk {
    fn sector_count(&self) -> u64 {
        (self.data.lock().len() / SECTOR_SIZE) as u64
    }
    fn read_sectors(&self, lba: u64, buf: &mut [u8]) -> Result<(), Errno> {
        check_range(self, lba, buf.len())?;
        let start = lba as usize * SECTOR_SIZE;
        buf.copy_from_slice(&self.data.lock()[start..start + buf.len()]);
        Ok(())
    }
    fn write_sectors(&self, lba: u64, buf: &[u8]) -> Result<(), Errno> {
        check_range(self, lba, buf.len())?;
        let start = lba as usize * SECTOR_SIZE;
        self.data.lock()[start..start + buf.len()].copy_from_slice(buf);
        Ok(())
    }
}

static DEVICES: Mutex<Vec<(String, Arc<dyn BlockDevice>)>> = Mutex::new(Vec::new());

pub fn register(name: &str, dev: Arc<dyn BlockDevice>) {
    DEVICES.lock().push((String::from(name), dev));
}

/// All registered devices, (name, device), in registration order.
pub fn devices() -> Vec<(String, Arc<dyn BlockDevice>)> {
    DEVICES.lock().clone()
}

/// Stage 21: the partition type of each registered partition ("vdb1" ->
/// "Linux root (x86-64)"), so fs::mount_disks can find the root.
static PART_TYPES: Mutex<Vec<(String, &'static str)>> = Mutex::new(Vec::new());

/// The partition-table type of device `name`, if it is a partition.
pub fn partition_type(name: &str) -> Option<&'static str> {
    PART_TYPES.lock().iter().find(|(n, _)| n == name).map(|(_, t)| *t)
}

pub fn get(name: &str) -> Option<Arc<dyn BlockDevice>> {
    DEVICES.lock().iter().find(|(n, _)| n == name).map(|(_, d)| d.clone())
}

/// Boot-time storage setup: find virtio disks on the PCI bus ("vda", "vdb"...),
/// register them, and register their partitions ("vda1", ...). Returns
/// lines describing what was found, for printing.
pub fn init(pci_devices: &[crate::pci::PciDevice]) -> Vec<String> {
    use alloc::format;
    let mut log = Vec::new();
    let mut letter = b'a';
    for pci in pci_devices.iter().filter(|d| virtio_blk::is_virtio_blk(d)) {
        let name = format!("vd{}", letter as char);
        match virtio_blk::VirtioBlk::new(pci) {
            Ok(disk) => {
                let disk: Arc<dyn BlockDevice> = Arc::new(disk);
                log.push(format!(
                    "{}: virtio-blk, {} sectors ({} MiB)",
                    name,
                    disk.sector_count(),
                    disk.sector_count() * SECTOR_SIZE as u64 / (1024 * 1024)
                ));
                register(&name, disk.clone());
                match partition::scan(&disk) {
                    Ok((scheme, parts)) => {
                        log.push(format!("{}: {} partition table, {} partition(s)", name, scheme, parts.len()));
                        for (i, p) in parts.into_iter().enumerate() {
                            let pname = format!("{}{}", name, i + 1);
                            log.push(format!(
                                "{}: start {} sectors {} type {}{}",
                                pname, p.start, p.sectors, p.type_name,
                                if p.name.is_empty() { String::new() } else { format!(" name \"{}\"", p.name) }
                            ));
                            register(&pname, Arc::new(partition::Partition::new(disk.clone(), p.start, p.sectors)));
                            PART_TYPES.lock().push((pname, p.type_name));
                        }
                    }
                    Err(e) => log.push(format!("{}: no partition table ({:?})", name, e)),
                }
            }
            Err(e) => log.push(format!("{}: virtio-blk init failed: {:?}", name, e)),
        }
        letter += 1;
    }
    log
}