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    memory::{UserSliceRead, UserSliceWrite},
31    syscall::error::SyscallError,
32    vfs::scheme::{
33        finalize_pseudo_stat, AsyncSubmitResult, DirEntry, FileFlags, FileStat, OpenFlags,
34        OpenResult, Scheme, DEV_DEVFS, DT_BLK, DT_CHR,
35    },
36};
37
38// ========== File-ID constants ==========
39
40const FID_ROOT: u64 = 0;
41const FID_SDA: u64 = 1;
42const FID_VDA: u64 = 2;
43const FID_NULL: u64 = 3;
44const FID_ZERO: u64 = 4;
45const FID_RANDOM: u64 = 5;
46const FID_URANDOM: u64 = 6;
47
48const RDEV_NULL: u64 = (1u64 << 8) | 3;
49const RDEV_ZERO: u64 = (1u64 << 8) | 5;
50const RDEV_RANDOM: u64 = (1u64 << 8) | 8;
51const RDEV_URANDOM: u64 = (1u64 << 8) | 9;
52const RDEV_SDA: u64 = (8u64 << 8) | 0;
53const RDEV_VDA: u64 = (254u64 << 8) | 0;
54
55// ========== xorshift64 fallback PRNG ====================================
56static PRNG_STATE: AtomicU64 = AtomicU64::new(0xdeadbeef_cafebabe);
57
58/// Performs the prng fill operation.
59fn prng_fill(buf: &mut [u8]) {
60    if crate::hardware::virtio::rng::is_available() {
61        let _ = crate::hardware::virtio::rng::read_entropy(buf);
62        return;
63    }
64    let ticks = crate::process::scheduler::ticks();
65    let mut state = PRNG_STATE.load(Ordering::Relaxed) ^ ticks;
66    for chunk in buf.chunks_mut(8) {
67        state ^= state << 13;
68        state ^= state >> 7;
69        state ^= state << 17;
70        let bytes = state.to_le_bytes();
71        let n = chunk.len();
72        chunk.copy_from_slice(&bytes[..n]);
73    }
74    PRNG_STATE.store(state, Ordering::Relaxed);
75}
76
77// ========== BlkDevScheme ==============================
78
79/// Kernel scheme that serves raw block devices as files under `/dev`.
80pub struct BlkDevScheme;
81
82fn try_direct_async_request(offset: u64, len: usize) -> Option<(u64, u32)> {
83    if len == 0 {
84        return Some((offset / SECTOR_SIZE as u64, 0));
85    }
86    if offset % SECTOR_SIZE as u64 != 0 || len % SECTOR_SIZE != 0 {
87        return None;
88    }
89    let lba = offset / SECTOR_SIZE as u64;
90    let byte_count = u32::try_from(len).ok()?;
91    Some((lba, byte_count))
92}
93
94impl BlkDevScheme {
95    /// Creates a new instance.
96    pub fn new() -> Self {
97        BlkDevScheme
98    }
99}
100
101impl Scheme for BlkDevScheme {
102    //  open ========================================
103
104    /// Performs the open operation.
105    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
106        match path.trim_start_matches('/') {
107            "" => {
108                // Root directory
109                Ok(OpenResult {
110                    file_id: FID_ROOT,
111                    size: None,
112                    flags: FileFlags::DIRECTORY,
113                })
114            }
115            "sda" => {
116                let dev = ahci::get_device().ok_or(SyscallError::NotFound)?;
117                Ok(OpenResult {
118                    file_id: FID_SDA,
119                    size: Some(dev.sector_count() * SECTOR_SIZE as u64),
120                    flags: FileFlags::DEVICE,
121                })
122            }
123            "vda" => {
124                let dev = crate::hardware::storage::virtio_block::get_device()
125                    .ok_or(SyscallError::NotFound)?;
126                Ok(OpenResult {
127                    file_id: FID_VDA,
128                    size: Some(dev.sector_count() * SECTOR_SIZE as u64),
129                    flags: FileFlags::DEVICE,
130                })
131            }
132            "null" => Ok(OpenResult {
133                file_id: FID_NULL,
134                size: Some(0),
135                flags: FileFlags::DEVICE,
136            }),
137            "zero" => Ok(OpenResult {
138                file_id: FID_ZERO,
139                size: None,
140                flags: FileFlags::DEVICE,
141            }),
142            "random" => Ok(OpenResult {
143                file_id: FID_RANDOM,
144                size: None,
145                flags: FileFlags::DEVICE,
146            }),
147            "urandom" => Ok(OpenResult {
148                file_id: FID_URANDOM,
149                size: None,
150                flags: FileFlags::DEVICE,
151            }),
152            _ => Err(SyscallError::NotFound),
153        }
154    }
155
156    //  read ========================================
157
158    /// Performs the read operation.
159    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
160        match file_id {
161            FID_ROOT => {
162                let mut listing = String::new();
163                listing.push_str("null\nurandom\nzero\nrandom\n");
164                if ahci::get_device().is_some() {
165                    listing.push_str("sda\n");
166                }
167                if crate::hardware::storage::virtio_block::get_device().is_some() {
168                    listing.push_str("vda\n");
169                }
170                let bytes = listing.as_bytes();
171                let start = offset as usize;
172                if start >= bytes.len() {
173                    return Ok(0);
174                }
175                let n = (bytes.len() - start).min(buf.len());
176                buf[..n].copy_from_slice(&bytes[start..start + n]);
177                Ok(n)
178            }
179            FID_NULL => Ok(0),
180            FID_ZERO => {
181                buf.fill(0);
182                Ok(buf.len())
183            }
184            FID_RANDOM | FID_URANDOM => {
185                prng_fill(buf);
186                Ok(buf.len())
187            }
188            FID_SDA => {
189                let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
190                sector_read(dev, offset, buf).map_err(|_| SyscallError::IoError)
191            }
192            FID_VDA => {
193                let dev = crate::hardware::storage::virtio_block::get_device()
194                    .ok_or(SyscallError::BadHandle)?;
195                sector_read(dev, offset, buf).map_err(|_| SyscallError::IoError)
196            }
197            _ => Err(SyscallError::BadHandle),
198        }
199    }
200
201    //  write ========================================
202
203    /// Performs the write operation.
204    fn write(&self, file_id: u64, offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
205        match file_id {
206            FID_NULL => Ok(buf.len()),
207            FID_ZERO | FID_RANDOM | FID_URANDOM => Err(SyscallError::PermissionDenied),
208            FID_SDA => {
209                let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
210                sector_write(dev, offset, buf).map_err(|_| SyscallError::IoError)
211            }
212            FID_VDA => {
213                let dev = crate::hardware::storage::virtio_block::get_device()
214                    .ok_or(SyscallError::BadHandle)?;
215                sector_write(dev, offset, buf).map_err(|_| SyscallError::IoError)
216            }
217            _ => Err(SyscallError::PermissionDenied),
218        }
219    }
220
221    //  close ========================================
222
223    /// Performs the close operation.
224    fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
225        Ok(()) // stateless: nothing to clean up
226    }
227
228    fn async_read(
229        &self,
230        file_id: u64,
231        offset: u64,
232        user_buf_vaddr: u64,
233        len: usize,
234        ring_id: u64,
235        user_data: u64,
236    ) -> Result<AsyncSubmitResult, SyscallError> {
237        if file_id == FID_SDA {
238            let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
239            if let Some((lba, byte_count)) = try_direct_async_request(offset, len) {
240                let port_idx = dev.first_port_num().ok_or(SyscallError::BadHandle)?;
241                ahci::submit_async_storage_op(
242                    port_idx,
243                    lba,
244                    byte_count,
245                    user_buf_vaddr,
246                    false,
247                    ring_id,
248                    user_data,
249                )
250                .map_err(|_| SyscallError::IoError)?;
251                return Ok(AsyncSubmitResult::InFlight);
252            }
253        }
254
255        // Guard against userspace-controlled sizes causing kernel OOM.
256        const MAX_SYNC_FALLBACK_LEN: usize = 4 * 1024 * 1024; // 4 MiB
257        if len > MAX_SYNC_FALLBACK_LEN {
258            return Err(SyscallError::InvalidArgument);
259        }
260        let user_buf = UserSliceWrite::new(user_buf_vaddr, len)?;
261        let mut kernel_buf = alloc::vec![0u8; len];
262        let n = self.read(file_id, offset, &mut kernel_buf)?;
263        user_buf.copy_from(&kernel_buf[..n]);
264        Ok(AsyncSubmitResult::Completed(n as i32))
265    }
266
267    fn async_write(
268        &self,
269        file_id: u64,
270        offset: u64,
271        user_buf_vaddr: u64,
272        len: usize,
273        ring_id: u64,
274        user_data: u64,
275    ) -> Result<AsyncSubmitResult, SyscallError> {
276        if file_id == FID_SDA {
277            let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
278            if let Some((lba, byte_count)) = try_direct_async_request(offset, len) {
279                let port_idx = dev.first_port_num().ok_or(SyscallError::BadHandle)?;
280                ahci::submit_async_storage_op(
281                    port_idx,
282                    lba,
283                    byte_count,
284                    user_buf_vaddr,
285                    true,
286                    ring_id,
287                    user_data,
288                )
289                .map_err(|_| SyscallError::IoError)?;
290                return Ok(AsyncSubmitResult::InFlight);
291            }
292        }
293
294        // Guard against userspace-controlled sizes causing kernel OOM.
295        const MAX_SYNC_FALLBACK_LEN: usize = 4 * 1024 * 1024; // 4 MiB
296        if len > MAX_SYNC_FALLBACK_LEN {
297            return Err(SyscallError::InvalidArgument);
298        }
299        let user_buf = UserSliceRead::new(user_buf_vaddr, len)?;
300        let kernel_buf = user_buf.read_to_vec();
301        let n = self.write(file_id, offset, &kernel_buf)?;
302        Ok(AsyncSubmitResult::Completed(n as i32))
303    }
304
305    //  size ========================================
306
307    /// Performs the size operation.
308    fn size(&self, file_id: u64) -> Result<u64, SyscallError> {
309        if file_id == FID_SDA {
310            let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
311            return Ok(dev.sector_count() * SECTOR_SIZE as u64);
312        }
313        if file_id == FID_VDA {
314            let dev = crate::hardware::storage::virtio_block::get_device()
315                .ok_or(SyscallError::BadHandle)?;
316            return Ok(dev.sector_count() * SECTOR_SIZE as u64);
317        }
318        Err(SyscallError::BadHandle)
319    }
320
321    //  stat ========================================
322
323    /// Performs the stat operation.
324    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
325        match file_id {
326            FID_ROOT => Ok(finalize_pseudo_stat(
327                FileStat {
328                    st_ino: FID_ROOT,
329                    st_mode: 0o040_755,
330                    st_nlink: 2,
331                    st_size: 0,
332                    st_blksize: SECTOR_SIZE as u64,
333                    st_blocks: 0,
334                    ..FileStat::zeroed()
335                },
336                DEV_DEVFS,
337                0,
338            )),
339            FID_NULL => Ok(finalize_pseudo_stat(
340                FileStat {
341                    st_ino: FID_NULL,
342                    st_mode: 0o020_666,
343                    st_nlink: 1,
344                    st_size: 0,
345                    st_blksize: 1,
346                    st_blocks: 0,
347                    ..FileStat::zeroed()
348                },
349                DEV_DEVFS,
350                RDEV_NULL,
351            )),
352            FID_ZERO => Ok(finalize_pseudo_stat(
353                FileStat {
354                    st_ino: FID_ZERO,
355                    st_mode: 0o020_444,
356                    st_nlink: 1,
357                    st_size: 0,
358                    st_blksize: 1,
359                    st_blocks: 0,
360                    ..FileStat::zeroed()
361                },
362                DEV_DEVFS,
363                RDEV_ZERO,
364            )),
365            FID_RANDOM => Ok(finalize_pseudo_stat(
366                FileStat {
367                    st_ino: FID_RANDOM,
368                    st_mode: 0o020_444,
369                    st_nlink: 1,
370                    st_size: 0,
371                    st_blksize: 1,
372                    st_blocks: 0,
373                    ..FileStat::zeroed()
374                },
375                DEV_DEVFS,
376                RDEV_RANDOM,
377            )),
378            FID_URANDOM => Ok(finalize_pseudo_stat(
379                FileStat {
380                    st_ino: FID_URANDOM,
381                    st_mode: 0o020_444,
382                    st_nlink: 1,
383                    st_size: 0,
384                    st_blksize: 1,
385                    st_blocks: 0,
386                    ..FileStat::zeroed()
387                },
388                DEV_DEVFS,
389                RDEV_URANDOM,
390            )),
391            FID_SDA => {
392                let dev = ahci::get_device().ok_or(SyscallError::BadHandle)?;
393                let size = dev.sector_count() * SECTOR_SIZE as u64;
394                Ok(finalize_pseudo_stat(
395                    FileStat {
396                        st_ino: FID_SDA,
397                        st_mode: 0o060_660, // brw-rw---- (block device)
398                        st_nlink: 1,
399                        st_size: size,
400                        st_blksize: SECTOR_SIZE as u64,
401                        st_blocks: dev.sector_count(),
402                        ..FileStat::zeroed()
403                    },
404                    DEV_DEVFS,
405                    RDEV_SDA,
406                ))
407            }
408            FID_VDA => {
409                let dev = crate::hardware::storage::virtio_block::get_device()
410                    .ok_or(SyscallError::BadHandle)?;
411                let size = dev.sector_count() * SECTOR_SIZE as u64;
412                Ok(finalize_pseudo_stat(
413                    FileStat {
414                        st_ino: FID_VDA,
415                        st_mode: 0o060_660,
416                        st_nlink: 1,
417                        st_size: size,
418                        st_blksize: SECTOR_SIZE as u64,
419                        st_blocks: dev.sector_count(),
420                        ..FileStat::zeroed()
421                    },
422                    DEV_DEVFS,
423                    RDEV_VDA,
424                ))
425            }
426            _ => Err(SyscallError::BadHandle),
427        }
428    }
429
430    //  readdir ==============================
431
432    /// Performs the readdir operation.
433    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
434        if file_id != FID_ROOT {
435            return Err(SyscallError::InvalidArgument);
436        }
437        let mut entries = Vec::new();
438        entries.push(DirEntry {
439            ino: FID_NULL,
440            file_type: DT_CHR,
441            name: String::from("null"),
442        });
443        entries.push(DirEntry {
444            ino: FID_ZERO,
445            file_type: DT_CHR,
446            name: String::from("zero"),
447        });
448        entries.push(DirEntry {
449            ino: FID_RANDOM,
450            file_type: DT_CHR,
451            name: String::from("random"),
452        });
453        entries.push(DirEntry {
454            ino: FID_URANDOM,
455            file_type: DT_CHR,
456            name: String::from("urandom"),
457        });
458        if ahci::get_device().is_some() {
459            entries.push(DirEntry {
460                ino: FID_SDA,
461                file_type: DT_BLK,
462                name: String::from("sda"),
463            });
464        }
465        if crate::hardware::storage::virtio_block::get_device().is_some() {
466            entries.push(DirEntry {
467                ino: FID_VDA,
468                file_type: DT_BLK,
469                name: String::from("vda"),
470            });
471        }
472        Ok(entries)
473    }
474}
475
476// ========== Byte-offset <-> sector I/O helpers ======================================================
477
478/// Read `buf.len()` bytes from the block device starting at byte `offset`.
479///
480/// Handles unaligned starts and ends by reading the affected sectors into a
481/// 512-byte stack buffer and copying only the requested range.
482fn sector_read<D: BlockDevice>(dev: &D, offset: u64, buf: &mut [u8]) -> Result<usize, BlockError> {
483    let total = buf.len();
484    if total == 0 {
485        return Ok(0);
486    }
487
488    let disk_size = BlockDevice::sector_count(dev) * SECTOR_SIZE as u64;
489    if offset >= disk_size {
490        return Ok(0); // EOF
491    }
492
493    let mut buf_pos: usize = 0;
494    let mut byte_off: u64 = offset;
495    // Clamp to disk boundary
496    let end = (offset + total as u64).min(disk_size);
497
498    while byte_off < end {
499        let sector = byte_off / SECTOR_SIZE as u64;
500        let sector_off = (byte_off % SECTOR_SIZE as u64) as usize;
501        let available = (SECTOR_SIZE - sector_off).min((end - byte_off) as usize);
502
503        let mut tmp = [0u8; SECTOR_SIZE];
504        dev.read_sector(sector, &mut tmp)?;
505
506        buf[buf_pos..buf_pos + available].copy_from_slice(&tmp[sector_off..sector_off + available]);
507
508        buf_pos += available;
509        byte_off += available as u64;
510    }
511
512    Ok(buf_pos)
513}
514
515/// Write `data` bytes to the block device starting at byte `offset`.
516///
517/// For partial-sector writes the affected sector is first read, patched in
518/// memory, then written back (read-modify-write).
519fn sector_write<D: BlockDevice>(dev: &D, offset: u64, data: &[u8]) -> Result<usize, BlockError> {
520    let total = data.len();
521    if total == 0 {
522        return Ok(0);
523    }
524
525    let disk_size = BlockDevice::sector_count(dev) * SECTOR_SIZE as u64;
526    if offset >= disk_size {
527        return Err(BlockError::InvalidSector);
528    }
529    let end = (offset + total as u64).min(disk_size);
530
531    let mut data_pos: usize = 0;
532    let mut byte_off: u64 = offset;
533
534    while data_pos < total && byte_off < end {
535        let sector = byte_off / SECTOR_SIZE as u64;
536        let sector_off = (byte_off % SECTOR_SIZE as u64) as usize;
537        let remaining = (end - byte_off) as usize;
538        let to_write = (SECTOR_SIZE - sector_off).min(remaining);
539
540        // Read-modify-write for partial sectors; full-sector writes skip the read
541        let mut tmp = [0u8; SECTOR_SIZE];
542        if sector_off != 0 || to_write != SECTOR_SIZE {
543            dev.read_sector(sector, &mut tmp)?;
544        }
545
546        tmp[sector_off..sector_off + to_write]
547            .copy_from_slice(&data[data_pos..data_pos + to_write]);
548        dev.write_sector(sector, &tmp)?;
549
550        data_pos += to_write;
551        byte_off += to_write as u64;
552    }
553
554    Ok(data_pos)
555}
556
557#[cfg(test)]
558mod tests {
559    use super::try_direct_async_request;
560
561    #[test]
562    fn direct_async_request_requires_sector_alignment() {
563        assert_eq!(try_direct_async_request(0, 512), Some((0, 512)));
564        assert_eq!(try_direct_async_request(512, 1024), Some((1, 1024)));
565        assert_eq!(try_direct_async_request(1, 512), None);
566        assert_eq!(try_direct_async_request(0, 513), None);
567    }
568}