Skip to main content

strat9_kernel/vfs/
blkdev_scheme.rs

1//! Block-device scheme — exposes raw disk devices under `/dev`.
2//!
3//! ## Namespace layout
4//!
5//! ```text
6//! /dev/          — directory listing (this scheme root)
7//! /dev/sda       — first SATA disk (AHCI port 0), raw byte-addressable
8//! ```
9//!
10//! ## I/O model
11//!
12//! The AHCI driver operates on 512-byte sectors.  This scheme accepts
13//! arbitrary byte offsets and lengths and performs the necessary
14//! sector-aligned read/modify/write internally using a stack-allocated
15//! 512-byte bounce buffer.
16//!
17//! ## VFS wiring
18//!
19//! `BlkDevScheme` implements the kernel `Scheme` trait so it can be mounted
20//! with `vfs::mount::mount("/dev", Arc::new(BlkDevScheme))` during `vfs::init()`.
21
22use alloc::{string::String, vec::Vec};
23use core::sync::atomic::{AtomicU64, Ordering};
24
25use crate::{
26    hardware::storage::{
27        ahci,
28        virtio_block::{BlockDevice, BlockError, SECTOR_SIZE},
29    },
30    syscall::error::SyscallError,
31    vfs::scheme::{
32        finalize_pseudo_stat, DirEntry, FileFlags, FileStat, OpenFlags, OpenResult, Scheme,
33        DEV_DEVFS, DT_BLK, DT_CHR,
34    },
35};
36
37// ─── File-ID constants ────────────────────────────────────────────────────────
38
39const FID_ROOT: u64 = 0;
40const FID_SDA: u64 = 1;
41const FID_VDA: u64 = 2;
42const FID_NULL: u64 = 3;
43const FID_ZERO: u64 = 4;
44const FID_RANDOM: u64 = 5;
45const FID_URANDOM: u64 = 6;
46
47const RDEV_NULL: u64 = (1u64 << 8) | 3;
48const RDEV_ZERO: u64 = (1u64 << 8) | 5;
49const RDEV_RANDOM: u64 = (1u64 << 8) | 8;
50const RDEV_URANDOM: u64 = (1u64 << 8) | 9;
51const RDEV_SDA: u64 = (8u64 << 8) | 0;
52const RDEV_VDA: u64 = (254u64 << 8) | 0;
53
54// ─── xorshift64 fallback PRNG ────────────────────────────────────────────────
55static PRNG_STATE: AtomicU64 = AtomicU64::new(0xdeadbeef_cafebabe);
56
57/// Performs the prng fill operation.
58fn prng_fill(buf: &mut [u8]) {
59    if crate::hardware::virtio::rng::is_available() {
60        let _ = crate::hardware::virtio::rng::read_entropy(buf);
61        return;
62    }
63    let ticks = crate::process::scheduler::ticks();
64    let mut state = PRNG_STATE.load(Ordering::Relaxed) ^ ticks;
65    for chunk in buf.chunks_mut(8) {
66        state ^= state << 13;
67        state ^= state >> 7;
68        state ^= state << 17;
69        let bytes = state.to_le_bytes();
70        let n = chunk.len();
71        chunk.copy_from_slice(&bytes[..n]);
72    }
73    PRNG_STATE.store(state, Ordering::Relaxed);
74}
75
76// ─── BlkDevScheme ─────────────────────────────────────────────────────────────
77
78/// Kernel scheme that serves raw block devices as files under `/dev`.
79pub struct BlkDevScheme;
80
81impl BlkDevScheme {
82    /// Creates a new instance.
83    pub fn new() -> Self {
84        BlkDevScheme
85    }
86}
87
88impl Scheme for BlkDevScheme {
89    // ── open ─────────────────────────────────────────────────────────────────
90
91    /// Performs the open operation.
92    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
93        match path.trim_start_matches('/') {
94            "" => {
95                // Root directory
96                Ok(OpenResult {
97                    file_id: FID_ROOT,
98                    size: None,
99                    flags: FileFlags::DIRECTORY,
100                })
101            }
102            "sda" => {
103                let dev = ahci::get_device().ok_or(SyscallError::NotFound)?;
104                Ok(OpenResult {
105                    file_id: FID_SDA,
106                    size: Some(dev.sector_count() * SECTOR_SIZE as u64),
107                    flags: FileFlags::DEVICE,
108                })
109            }
110            "vda" => {
111                let dev = crate::hardware::storage::virtio_block::get_device()
112                    .ok_or(SyscallError::NotFound)?;
113                Ok(OpenResult {
114                    file_id: FID_VDA,
115                    size: Some(dev.sector_count() * SECTOR_SIZE as u64),
116                    flags: FileFlags::DEVICE,
117                })
118            }
119            "null" => Ok(OpenResult {
120                file_id: FID_NULL,
121                size: Some(0),
122                flags: FileFlags::DEVICE,
123            }),
124            "zero" => Ok(OpenResult {
125                file_id: FID_ZERO,
126                size: None,
127                flags: FileFlags::DEVICE,
128            }),
129            "random" => Ok(OpenResult {
130                file_id: FID_RANDOM,
131                size: None,
132                flags: FileFlags::DEVICE,
133            }),
134            "urandom" => Ok(OpenResult {
135                file_id: FID_URANDOM,
136                size: None,
137                flags: FileFlags::DEVICE,
138            }),
139            _ => Err(SyscallError::NotFound),
140        }
141    }
142
143    // ── read ─────────────────────────────────────────────────────────────────
144
145    /// Performs the read operation.
146    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
147        match file_id {
148            FID_ROOT => {
149                let mut listing = String::new();
150                listing.push_str("null\nurandom\nzero\nrandom\n");
151                if ahci::get_device().is_some() {
152                    listing.push_str("sda\n");
153                }
154                if crate::hardware::storage::virtio_block::get_device().is_some() {
155                    listing.push_str("vda\n");
156                }
157                let bytes = listing.as_bytes();
158                let start = offset as usize;
159                if start >= bytes.len() {
160                    return Ok(0);
161                }
162                let n = (bytes.len() - start).min(buf.len());
163                buf[..n].copy_from_slice(&bytes[start..start + n]);
164                Ok(n)
165            }
166            FID_NULL => Ok(0),
167            FID_ZERO => {
168                buf.fill(0);
169                Ok(buf.len())
170            }
171            FID_RANDOM | FID_URANDOM => {
172                prng_fill(buf);
173                Ok(buf.len())
174            }
175            FID_SDA => {
176                let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
177                sector_read(dev, offset, buf).map_err(|_| SyscallError::IoError)
178            }
179            FID_VDA => {
180                let dev = crate::hardware::storage::virtio_block::get_device()
181                    .ok_or(SyscallError::BadHandle)?;
182                sector_read(dev, offset, buf).map_err(|_| SyscallError::IoError)
183            }
184            _ => Err(SyscallError::BadHandle),
185        }
186    }
187
188    // ── write ────────────────────────────────────────────────────────────────
189
190    /// Performs the write operation.
191    fn write(&self, file_id: u64, offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
192        match file_id {
193            FID_NULL => Ok(buf.len()),
194            FID_ZERO | FID_RANDOM | FID_URANDOM => Err(SyscallError::PermissionDenied),
195            FID_SDA => {
196                let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
197                sector_write(dev, offset, buf).map_err(|_| SyscallError::IoError)
198            }
199            FID_VDA => {
200                let dev = crate::hardware::storage::virtio_block::get_device()
201                    .ok_or(SyscallError::BadHandle)?;
202                sector_write(dev, offset, buf).map_err(|_| SyscallError::IoError)
203            }
204            _ => Err(SyscallError::PermissionDenied),
205        }
206    }
207
208    // ── close ────────────────────────────────────────────────────────────────
209
210    /// Performs the close operation.
211    fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
212        Ok(()) // stateless: nothing to clean up
213    }
214
215    // ── size ─────────────────────────────────────────────────────────────────
216
217    /// Performs the size operation.
218    fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
219        if file_id == FID_SDA {
220            let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
221            return Ok(dev.sector_count() * SECTOR_SIZE as u64);
222        }
223        if file_id == FID_VDA {
224            let dev = crate::hardware::storage::virtio_block::get_device()
225                .ok_or(SyscallError::BadHandle)?;
226            return Ok(dev.sector_count() * SECTOR_SIZE as u64);
227        }
228        Err(SyscallError::BadHandle)
229    }
230
231    // ── stat ─────────────────────────────────────────────────────────────────
232
233    /// Performs the stat operation.
234    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
235        match file_id {
236            FID_ROOT => Ok(finalize_pseudo_stat(
237                FileStat {
238                    st_ino: FID_ROOT,
239                    st_mode: 0o040_755,
240                    st_nlink: 2,
241                    st_size: 0,
242                    st_blksize: SECTOR_SIZE as u64,
243                    st_blocks: 0,
244                    ..FileStat::zeroed()
245                },
246                DEV_DEVFS,
247                0,
248            )),
249            FID_NULL => Ok(finalize_pseudo_stat(
250                FileStat {
251                    st_ino: FID_NULL,
252                    st_mode: 0o020_666,
253                    st_nlink: 1,
254                    st_size: 0,
255                    st_blksize: 1,
256                    st_blocks: 0,
257                    ..FileStat::zeroed()
258                },
259                DEV_DEVFS,
260                RDEV_NULL,
261            )),
262            FID_ZERO => Ok(finalize_pseudo_stat(
263                FileStat {
264                    st_ino: FID_ZERO,
265                    st_mode: 0o020_444,
266                    st_nlink: 1,
267                    st_size: 0,
268                    st_blksize: 1,
269                    st_blocks: 0,
270                    ..FileStat::zeroed()
271                },
272                DEV_DEVFS,
273                RDEV_ZERO,
274            )),
275            FID_RANDOM => Ok(finalize_pseudo_stat(
276                FileStat {
277                    st_ino: FID_RANDOM,
278                    st_mode: 0o020_444,
279                    st_nlink: 1,
280                    st_size: 0,
281                    st_blksize: 1,
282                    st_blocks: 0,
283                    ..FileStat::zeroed()
284                },
285                DEV_DEVFS,
286                RDEV_RANDOM,
287            )),
288            FID_URANDOM => Ok(finalize_pseudo_stat(
289                FileStat {
290                    st_ino: FID_URANDOM,
291                    st_mode: 0o020_444,
292                    st_nlink: 1,
293                    st_size: 0,
294                    st_blksize: 1,
295                    st_blocks: 0,
296                    ..FileStat::zeroed()
297                },
298                DEV_DEVFS,
299                RDEV_URANDOM,
300            )),
301            FID_SDA => {
302                let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
303                let size = dev.sector_count() * SECTOR_SIZE as u64;
304                Ok(finalize_pseudo_stat(
305                    FileStat {
306                        st_ino: FID_SDA,
307                        st_mode: 0o060_660, // brw-rw---- (block device)
308                        st_nlink: 1,
309                        st_size: size,
310                        st_blksize: SECTOR_SIZE as u64,
311                        st_blocks: dev.sector_count(),
312                        ..FileStat::zeroed()
313                    },
314                    DEV_DEVFS,
315                    RDEV_SDA,
316                ))
317            }
318            FID_VDA => {
319                let dev = crate::hardware::storage::virtio_block::get_device()
320                    .ok_or(SyscallError::BadHandle)?;
321                let size = dev.sector_count() * SECTOR_SIZE as u64;
322                Ok(finalize_pseudo_stat(
323                    FileStat {
324                        st_ino: FID_VDA,
325                        st_mode: 0o060_660,
326                        st_nlink: 1,
327                        st_size: size,
328                        st_blksize: SECTOR_SIZE as u64,
329                        st_blocks: dev.sector_count(),
330                        ..FileStat::zeroed()
331                    },
332                    DEV_DEVFS,
333                    RDEV_VDA,
334                ))
335            }
336            _ => Err(SyscallError::BadHandle),
337        }
338    }
339
340    // ── readdir ──────────────────────────────────────────────────────────────
341
342    /// Performs the readdir operation.
343    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
344        if file_id != FID_ROOT {
345            return Err(SyscallError::InvalidArgument);
346        }
347        let mut entries = Vec::new();
348        entries.push(DirEntry {
349            ino: FID_NULL,
350            file_type: DT_CHR,
351            name: String::from("null"),
352        });
353        entries.push(DirEntry {
354            ino: FID_ZERO,
355            file_type: DT_CHR,
356            name: String::from("zero"),
357        });
358        entries.push(DirEntry {
359            ino: FID_RANDOM,
360            file_type: DT_CHR,
361            name: String::from("random"),
362        });
363        entries.push(DirEntry {
364            ino: FID_URANDOM,
365            file_type: DT_CHR,
366            name: String::from("urandom"),
367        });
368        if ahci::get_device().is_some() {
369            entries.push(DirEntry {
370                ino: FID_SDA,
371                file_type: DT_BLK,
372                name: String::from("sda"),
373            });
374        }
375        if crate::hardware::storage::virtio_block::get_device().is_some() {
376            entries.push(DirEntry {
377                ino: FID_VDA,
378                file_type: DT_BLK,
379                name: String::from("vda"),
380            });
381        }
382        Ok(entries)
383    }
384}
385
386// ─── Byte-offset <==> sector I/O helpers ────────────────────────────────────────
387
388/// Read `buf.len()` bytes from the block device starting at byte `offset`.
389///
390/// Handles unaligned starts and ends by reading the affected sectors into a
391/// 512-byte stack buffer and copying only the requested range.
392fn sector_read<D: BlockDevice>(dev: &D, offset: u64, buf: &mut [u8]) -> Result<usize, BlockError> {
393    let total = buf.len();
394    if total == 0 {
395        return Ok(0);
396    }
397
398    let disk_size = BlockDevice::sector_count(dev) * SECTOR_SIZE as u64;
399    if offset >= disk_size {
400        return Ok(0); // EOF
401    }
402
403    let mut buf_pos: usize = 0;
404    let mut byte_off: u64 = offset;
405    // Clamp to disk boundary
406    let end = (offset + total as u64).min(disk_size);
407
408    while byte_off < end {
409        let sector = byte_off / SECTOR_SIZE as u64;
410        let sector_off = (byte_off % SECTOR_SIZE as u64) as usize;
411        let available = (SECTOR_SIZE - sector_off).min((end - byte_off) as usize);
412
413        let mut tmp = [0u8; SECTOR_SIZE];
414        dev.read_sector(sector, &mut tmp)?;
415
416        buf[buf_pos..buf_pos + available].copy_from_slice(&tmp[sector_off..sector_off + available]);
417
418        buf_pos += available;
419        byte_off += available as u64;
420    }
421
422    Ok(buf_pos)
423}
424
425/// Write `data` bytes to the block device starting at byte `offset`.
426///
427/// For partial-sector writes the affected sector is first read, patched in
428/// memory, then written back (read-modify-write).
429fn sector_write<D: BlockDevice>(dev: &D, offset: u64, data: &[u8]) -> Result<usize, BlockError> {
430    let total = data.len();
431    if total == 0 {
432        return Ok(0);
433    }
434
435    let disk_size = BlockDevice::sector_count(dev) * SECTOR_SIZE as u64;
436    if offset >= disk_size {
437        return Err(BlockError::InvalidSector);
438    }
439    let end = (offset + total as u64).min(disk_size);
440
441    let mut data_pos: usize = 0;
442    let mut byte_off: u64 = offset;
443
444    while data_pos < total && byte_off < end {
445        let sector = byte_off / SECTOR_SIZE as u64;
446        let sector_off = (byte_off % SECTOR_SIZE as u64) as usize;
447        let remaining = (end - byte_off) as usize;
448        let to_write = (SECTOR_SIZE - sector_off).min(remaining);
449
450        // Read-modify-write for partial sectors; full-sector writes skip the read
451        let mut tmp = [0u8; SECTOR_SIZE];
452        if sector_off != 0 || to_write != SECTOR_SIZE {
453            dev.read_sector(sector, &mut tmp)?;
454        }
455
456        tmp[sector_off..sector_off + to_write]
457            .copy_from_slice(&data[data_pos..data_pos + to_write]);
458        dev.write_sector(sector, &tmp)?;
459
460        data_pos += to_write;
461        byte_off += to_write as u64;
462    }
463
464    Ok(data_pos)
465}