mirror of
https://github.com/sgmarz/osblog.git
synced 2024-11-23 18:06:20 +04:00
Start adding block device stuff and file system stuff. Added test for block driver as a process.
This commit is contained in:
parent
599627b74a
commit
a60d84ed98
@ -6,7 +6,8 @@
|
||||
use crate::{kmem::{kfree, kmalloc},
|
||||
page::{zalloc, PAGE_SIZE},
|
||||
virtio,
|
||||
virtio::{Descriptor, MmioOffsets, Queue, StatusField, VIRTIO_RING_SIZE}};
|
||||
virtio::{Descriptor, MmioOffsets, Queue, StatusField, VIRTIO_RING_SIZE}};
|
||||
use crate::process::{set_running, set_waiting};
|
||||
use core::mem::size_of;
|
||||
|
||||
#[repr(C)]
|
||||
@ -79,6 +80,13 @@ pub struct Request {
|
||||
data: Data,
|
||||
status: Status,
|
||||
head: u16,
|
||||
|
||||
// Do not change anything above this line.
|
||||
// This is the PID of watcher. We store the PID
|
||||
// because it is possible that the process DIES
|
||||
// before we get here. If we used a pointer, we
|
||||
// may dereference invalid memory.
|
||||
watcher: u16,
|
||||
}
|
||||
|
||||
// Internal block device structure
|
||||
@ -118,6 +126,14 @@ pub const VIRTIO_BLK_F_CONFIG_WCE: u32 = 11;
|
||||
pub const VIRTIO_BLK_F_DISCARD: u32 = 13;
|
||||
pub const VIRTIO_BLK_F_WRITE_ZEROES: u32 = 14;
|
||||
|
||||
// We might get several types of errors, but they can be enumerated here.
|
||||
pub enum BlockErrors {
|
||||
Success = 0,
|
||||
BlockDeviceNotFound,
|
||||
InvalidArgument,
|
||||
ReadOnly
|
||||
}
|
||||
|
||||
// Much like with processes, Rust requires some initialization
|
||||
// when we declare a static. In this case, we use the Option
|
||||
// value type to signal that the variable exists, but not the
|
||||
@ -247,13 +263,16 @@ pub fn fill_next_descriptor(bd: &mut BlockDevice, desc: Descriptor) -> u16 {
|
||||
/// a multiple of 512, but we don't really check that.
|
||||
/// We DO however, check that we aren't writing to an R/O device. This would
|
||||
/// cause a I/O error if we tried to write to a R/O device.
|
||||
pub fn block_op(dev: usize, buffer: *mut u8, size: u32, offset: u64, write: bool) {
|
||||
pub fn block_op(dev: usize, buffer: *mut u8, size: u32, offset: u64, write: bool, watcher: u16) -> Result<u32, BlockErrors> {
|
||||
unsafe {
|
||||
if let Some(bdev) = BLOCK_DEVICES[dev - 1].as_mut() {
|
||||
// Check to see if we are trying to write to a read only device.
|
||||
if true == bdev.read_only && true == write {
|
||||
if bdev.read_only && write {
|
||||
println!("Trying to write to read/only!");
|
||||
return;
|
||||
return Err(BlockErrors::ReadOnly);
|
||||
}
|
||||
if size % 512 != 0 {
|
||||
return Err(BlockErrors::InvalidArgument);
|
||||
}
|
||||
let sector = offset / 512;
|
||||
// TODO: Before we get here, we are NOT allowed to schedule a read or
|
||||
@ -268,7 +287,7 @@ pub fn block_op(dev: usize, buffer: *mut u8, size: u32, offset: u64, write: bool
|
||||
let head_idx = fill_next_descriptor(bdev, desc);
|
||||
(*blk_request).header.sector = sector;
|
||||
// A write is an "out" direction, whereas a read is an "in" direction.
|
||||
(*blk_request).header.blktype = if true == write {
|
||||
(*blk_request).header.blktype = if write {
|
||||
VIRTIO_BLK_T_OUT
|
||||
}
|
||||
else {
|
||||
@ -280,10 +299,11 @@ pub fn block_op(dev: usize, buffer: *mut u8, size: u32, offset: u64, write: bool
|
||||
(*blk_request).data.data = buffer;
|
||||
(*blk_request).header.reserved = 0;
|
||||
(*blk_request).status.status = 111;
|
||||
(*blk_request).watcher = watcher;
|
||||
let desc = Descriptor { addr: buffer as u64,
|
||||
len: size,
|
||||
flags: virtio::VIRTIO_DESC_F_NEXT
|
||||
| if false == write {
|
||||
| if !write {
|
||||
virtio::VIRTIO_DESC_F_WRITE
|
||||
}
|
||||
else {
|
||||
@ -301,16 +321,20 @@ pub fn block_op(dev: usize, buffer: *mut u8, size: u32, offset: u64, write: bool
|
||||
// The only queue a block device has is 0, which is the request
|
||||
// queue.
|
||||
bdev.dev.add(MmioOffsets::QueueNotify.scale32()).write_volatile(0);
|
||||
Ok(size)
|
||||
}
|
||||
else {
|
||||
Err(BlockErrors::BlockDeviceNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(dev: usize, buffer: *mut u8, size: u32, offset: u64) {
|
||||
block_op(dev, buffer, size, offset, false);
|
||||
pub fn read(dev: usize, buffer: *mut u8, size: u32, offset: u64) -> Result<u32, BlockErrors> {
|
||||
block_op(dev, buffer, size, offset, false, 0)
|
||||
}
|
||||
|
||||
pub fn write(dev: usize, buffer: *mut u8, size: u32, offset: u64) {
|
||||
block_op(dev, buffer, size, offset, true);
|
||||
pub fn write(dev: usize, buffer: *mut u8, size: u32, offset: u64) -> Result<u32, BlockErrors> {
|
||||
block_op(dev, buffer, size, offset, true, 0)
|
||||
}
|
||||
|
||||
/// Here we handle block specific interrupts. Here, we need to check
|
||||
@ -324,10 +348,17 @@ pub fn pending(bd: &mut BlockDevice) {
|
||||
while bd.ack_used_idx != queue.used.idx {
|
||||
let ref elem = queue.used.ring[bd.ack_used_idx as usize % VIRTIO_RING_SIZE];
|
||||
bd.ack_used_idx = bd.ack_used_idx.wrapping_add(1);
|
||||
// Requests stay resident on the heap until this function, so we can recapture the address here
|
||||
let rq = queue.desc[elem.id as usize].addr as *const Request;
|
||||
|
||||
// A process might be waiting for this interrupt. Awaken the process attached here.
|
||||
let pid_of_watcher = (*rq).watcher;
|
||||
// A PID of 0 means that we don't have a watcher.
|
||||
if pid_of_watcher > 0 {
|
||||
set_running(pid_of_watcher);
|
||||
// TODO: Set GpA0 to the value of the return status.
|
||||
}
|
||||
kfree(rq as *mut u8);
|
||||
// TODO: Awaken the process that will need this I/O. This is
|
||||
// the purpose of the waiting state.
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -344,3 +375,14 @@ pub fn handle_interrupt(idx: usize) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_read(pid: u16, dev: usize, buffer: *mut u8, size: u32, offset: u64) -> Result<u32, BlockErrors> {
|
||||
println!("Process read {}, {}, 0x{:x}, {}, {}", pid, dev, buffer as usize, size, offset);
|
||||
set_waiting(pid);
|
||||
block_op(dev, buffer, size, offset, false, pid)
|
||||
}
|
||||
|
||||
pub fn process_write(pid: u16, dev: usize, buffer: *mut u8, size: u32, offset: u64) -> Result<u32, BlockErrors> {
|
||||
set_waiting(pid);
|
||||
block_op(dev, buffer, size, offset, true, pid)
|
||||
}
|
@ -27,8 +27,10 @@ pub struct Stat {
|
||||
|
||||
/// A file descriptor
|
||||
pub struct Descriptor {
|
||||
blockdev: usize,
|
||||
node: u32,
|
||||
pub blockdev: usize,
|
||||
pub node: u32,
|
||||
pub loc: u32,
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
pub enum FsError {
|
||||
|
@ -89,6 +89,18 @@ pub fn init() {
|
||||
}
|
||||
}
|
||||
|
||||
/// talloc and tfree will allocate a reference to a given type
|
||||
/// This helps when creating structures.
|
||||
pub fn talloc<T>() -> Option<&'static mut T> {
|
||||
unsafe {
|
||||
(kzmalloc(size_of::<T>()) as *mut T).as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tfree<T>(p: *mut T) {
|
||||
kfree(p as *mut u8);
|
||||
}
|
||||
|
||||
/// Allocate sub-page level allocation based on bytes and zero the memory
|
||||
pub fn kzmalloc(sz: usize) -> *mut u8 {
|
||||
let size = align_val(sz, 3);
|
||||
|
@ -122,6 +122,7 @@ pub fn id_map_range(root: &mut page::Table,
|
||||
extern "C" {
|
||||
fn switch_to_user(frame: usize) -> !;
|
||||
}
|
||||
|
||||
fn rust_switch_to_user(frame: usize) -> ! {
|
||||
unsafe {
|
||||
switch_to_user(frame);
|
||||
@ -149,18 +150,8 @@ extern "C" fn kinit() {
|
||||
}
|
||||
// Set up virtio. This requires a working heap and page-grained allocator.
|
||||
virtio::probe();
|
||||
// Let's test the block driver!
|
||||
println!("Testing block driver.");
|
||||
let buffer = kmem::kmalloc(512);
|
||||
block::read(8, buffer, 512, 0);
|
||||
for i in 0..48 {
|
||||
print!(" {:02x}", unsafe { buffer.add(i).read() });
|
||||
if 0 == ((i+1) % 24) {
|
||||
println!();
|
||||
}
|
||||
}
|
||||
kmem::kfree(buffer);
|
||||
println!("Block driver done");
|
||||
// Test the block driver!
|
||||
process::add_kernel_process(test::test_block);
|
||||
// We schedule the next context switch using a multiplier of 1
|
||||
// Block testing code removed.
|
||||
trap::schedule_next_context_switch(1);
|
||||
@ -192,7 +183,6 @@ pub mod syscall;
|
||||
pub mod trap;
|
||||
pub mod uart;
|
||||
pub mod virtio;
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
||||
|
||||
|
@ -3,77 +3,232 @@
|
||||
// Stephen Marz
|
||||
// 16 March 2020
|
||||
|
||||
use crate::fs::{Descriptor, FileSystem, Stat, FsError};
|
||||
|
||||
use crate::{block,
|
||||
fs::{Descriptor, FileSystem, FsError, Stat},
|
||||
kmem::{kfree, kmalloc, talloc, tfree}};
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
use core::{mem::size_of, ptr::null_mut};
|
||||
|
||||
pub const MAGIC: u16 = 0x4d5a;
|
||||
pub const BLOCK_SIZE: u32 = 1024;
|
||||
pub const NUM_IPTRS: u32 = BLOCK_SIZE / 4;
|
||||
|
||||
/// The superblock describes the file system on the disk. It gives
|
||||
/// us all the information we need to read the file system and navigate
|
||||
/// the file system, including where to find the inodes and zones (blocks).
|
||||
#[repr(C)]
|
||||
pub struct SuperBlock {
|
||||
ninodes: u32,
|
||||
pad0: u16,
|
||||
imap_blocks: u16,
|
||||
zmap_blocks: u16,
|
||||
first_data_zone: u16,
|
||||
log_zone_size: u16,
|
||||
pad1: u16,
|
||||
max_size: u32,
|
||||
zones: u32,
|
||||
magic: u16,
|
||||
pad2: u16,
|
||||
block_size: u16,
|
||||
disk_version: u8,
|
||||
pub ninodes: u32,
|
||||
pub pad0: u16,
|
||||
pub imap_blocks: u16,
|
||||
pub zmap_blocks: u16,
|
||||
pub first_data_zone: u16,
|
||||
pub log_zone_size: u16,
|
||||
pub pad1: u16,
|
||||
pub max_size: u32,
|
||||
pub zones: u32,
|
||||
pub magic: u16,
|
||||
pub pad2: u16,
|
||||
pub block_size: u16,
|
||||
pub disk_version: u8,
|
||||
}
|
||||
|
||||
/// An inode stores the "meta-data" to a file. The mode stores the permissions
|
||||
/// AND type of file. This is how we differentiate a directory from a file. A file
|
||||
/// size is in here too, which tells us how many blocks we need to read. Finally, the
|
||||
/// zones array points to where we can find the blocks, which is where the data
|
||||
/// is contained for the file.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Inode {
|
||||
mode: u16,
|
||||
nlinks: u16,
|
||||
uid: u16,
|
||||
gid: u16,
|
||||
size: u32,
|
||||
atime: u32,
|
||||
mtime: u32,
|
||||
ctime: u32,
|
||||
zones: [u32; 10],
|
||||
pub mode: u16,
|
||||
pub nlinks: u16,
|
||||
pub uid: u16,
|
||||
pub gid: u16,
|
||||
pub size: u32,
|
||||
pub atime: u32,
|
||||
pub mtime: u32,
|
||||
pub ctime: u32,
|
||||
pub zones: [u32; 10],
|
||||
}
|
||||
|
||||
/// Notice that an inode does not contain the name of a file. This is because
|
||||
/// more than one file name may refer to the same inode. These are called "hard links"
|
||||
/// Instead, a DirEntry essentially associates a file name with an inode as shown in
|
||||
/// the structure below.
|
||||
#[repr(C)]
|
||||
pub struct DirEntry {
|
||||
inode: u32,
|
||||
name: [u8; 60],
|
||||
pub inode: u32,
|
||||
pub name: [u8; 60],
|
||||
}
|
||||
|
||||
pub struct MinixFileSystem {
|
||||
sb: SuperBlock
|
||||
// We need a BlockBuffer that can automatically be created and destroyed
|
||||
// in the lifetime of our read and write functions. In C, this would entail
|
||||
// goto statements that "unravel" all of the allocations that we made. Take
|
||||
// a look at the read() function to see why I thought this way would be better.
|
||||
pub struct BlockBuffer {
|
||||
buffer: *mut u8,
|
||||
}
|
||||
|
||||
impl BlockBuffer {
|
||||
pub fn new(sz: u32) -> Self {
|
||||
BlockBuffer { buffer: kmalloc(sz as usize), }
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> *mut u8 {
|
||||
self.buffer
|
||||
}
|
||||
|
||||
pub fn get(&self) -> *const u8 {
|
||||
self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BlockBuffer {
|
||||
fn default() -> Self {
|
||||
BlockBuffer { buffer: kmalloc(1024), }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BlockBuffer {
|
||||
fn drop(&mut self) {
|
||||
if !self.buffer.is_null() {
|
||||
kfree(self.buffer);
|
||||
self.buffer = null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The MinixFileSystem implements the FileSystem trait for the VFS.
|
||||
pub struct MinixFileSystem;
|
||||
|
||||
impl MinixFileSystem {
|
||||
/// Inodes are the meta-data of a file, including the mode (permissions and type) and
|
||||
/// the file's size. They are stored above the data zones, but to figure out where we
|
||||
/// need to go to get the inode, we first need the superblock, which is where we can
|
||||
/// find all of the information about the filesystem itself.
|
||||
pub fn get_inode(bdev: usize, inode_num: u32) -> Option<Inode> {
|
||||
// When we read, everything needs to be a multiple of a sector (512 bytes)
|
||||
// So, we need to have memory available that's at least 512 bytes, even if
|
||||
// we only want 10 bytes or 32 bytes (size of an Inode).
|
||||
let mut buffer = BlockBuffer::new(512);
|
||||
|
||||
// Here is a little memory trick. We have a reference and it will refer to the
|
||||
// top portion of our buffer. Since we won't be using the super block and inode
|
||||
// simultaneously, we can overlap the memory regions.
|
||||
let super_block = unsafe { &*(buffer.get_mut() as *mut SuperBlock) };
|
||||
let inode = unsafe { &*(buffer.get_mut() as *mut Inode) };
|
||||
|
||||
// Read from the block device. The size is 1 sector (512 bytes) and our offset is past
|
||||
// the boot block (first 1024 bytes). This is where the superblock sits.
|
||||
let result = block::read(bdev, buffer.get_mut(), 512, 1024);
|
||||
for _ in 0..1000000 {
|
||||
|
||||
}
|
||||
if result.is_ok() && super_block.magic == MAGIC {
|
||||
// If we get here, we successfully read what we think is the super block.
|
||||
// The math here is 2 - one for the boot block, one for the super block. Then we
|
||||
// have to skip the bitmaps blocks. We have a certain number of inode map blocks (imap)
|
||||
// and zone map blocks (zmap).
|
||||
// The inode comes to us as a NUMBER, not an index. So, we need to subtract 1.
|
||||
let inode_offset = (2 + super_block.imap_blocks + super_block.zmap_blocks) as usize
|
||||
* BLOCK_SIZE as usize + (inode_num as usize - 1) * size_of::<Inode>();
|
||||
|
||||
// Now, we read the inode itself.
|
||||
let result = block::read(bdev, buffer.get_mut(), 512, inode_offset as u64);
|
||||
for _ in 0..1000000 {
|
||||
|
||||
}
|
||||
if result.is_ok() {
|
||||
println!("Inode sizex = {} {:o}", inode.size, inode.mode);
|
||||
return Some(inode.clone());
|
||||
}
|
||||
}
|
||||
// If we get here, some result wasn't OK. Either the super block
|
||||
// or the inode itself.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSystem for MinixFileSystem {
|
||||
fn init(_bdev: usize) -> bool {
|
||||
false
|
||||
}
|
||||
fn open(_path: &String) -> Result<Descriptor, FsError> {
|
||||
Err(FsError::FileNotFound)
|
||||
}
|
||||
fn read(_desc: &Descriptor, _buffer: *mut u8, _offset: u32, _size: u32) -> u32 {
|
||||
0
|
||||
}
|
||||
fn write(_desc: &Descriptor, _buffer: *const u8, _offset: u32, _size: u32) -> u32 {
|
||||
0
|
||||
}
|
||||
fn close(_desc: &mut Descriptor) {
|
||||
fn init(_bdev: usize) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
fn stat(_desc: &Descriptor) -> Stat {
|
||||
Stat {
|
||||
mode: 0,
|
||||
size: 0,
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
}
|
||||
}
|
||||
fn open(_path: &String) -> Result<Descriptor, FsError> {
|
||||
Err(FsError::FileNotFound)
|
||||
}
|
||||
|
||||
fn read(desc: &Descriptor, buffer: *mut u8, offset: u32, size: u32) -> u32 {
|
||||
let mut blocks_seen = 0u32;
|
||||
let offset_block = offset / BLOCK_SIZE;
|
||||
let offset_byte = offset % BLOCK_SIZE;
|
||||
|
||||
let mut block_buffer = BlockBuffer::new(BLOCK_SIZE);
|
||||
let stats = Self::stat(desc);
|
||||
let inode_result = Self::get_inode(desc.blockdev, desc.node);
|
||||
if inode_result.is_none() {
|
||||
// The inode couldn't be read, for some reason.
|
||||
return 0;
|
||||
}
|
||||
let inode = inode_result.unwrap();
|
||||
// First, the _size parameter (now in bytes_left) is the size of the buffer, not
|
||||
// necessarily the size of the file. If our buffer is bigger than the file, we're OK.
|
||||
// If our buffer is smaller than the file, then we can only read up to the buffer size.
|
||||
let mut bytes_left = if size > stats.size {
|
||||
stats.size
|
||||
}
|
||||
else {
|
||||
size
|
||||
};
|
||||
let mut bytes_left = 0;
|
||||
let mut bytes_read = 0u32;
|
||||
// In Rust, our for loop automatically "declares" i from 0 to < 7. The syntax
|
||||
// 0..7 means 0 through to 7 but not including 7. If we want to include 7, we
|
||||
// would use the syntax 0..=7.
|
||||
for i in 0..7 {
|
||||
// There are 7 direct zones in the Minix 3 file system. So, we can just read them
|
||||
// one by one. Any zone that has the value 0 is skipped and we check the next
|
||||
// zones. This might happen as we start writing and truncating.
|
||||
|
||||
// We really use this to keep track of when we need to actually start reading
|
||||
// But an if statement probably takes more time than just incrementing it.
|
||||
if offset_block <= blocks_seen {
|
||||
// If we get here, then our offset is within our window that we want to see.
|
||||
// We need to go to the direct pointer's index. That'll give us a block INDEX.
|
||||
// That makes it easy since all we have to do is multiply the block size
|
||||
// by whatever we get. If it's 0, we skip it and move on.
|
||||
let zone_num = inode.zones[i];
|
||||
if zone_num == 0 {
|
||||
continue;
|
||||
}
|
||||
let zone_offset = zone_num * BLOCK_SIZE;
|
||||
println!("Zone #{} -> #{} -> {}", i, zone_num, zone_offset);
|
||||
if let Ok(_) = block::read(desc.blockdev, block_buffer.get_mut(), BLOCK_SIZE, zone_offset as u64) {
|
||||
for _ in 0..100000 {}
|
||||
println!("Offset = {:x}", unsafe {block_buffer.get_mut().add(32).read()});
|
||||
}
|
||||
}
|
||||
blocks_seen += 1;
|
||||
}
|
||||
|
||||
bytes_read
|
||||
}
|
||||
|
||||
fn write(_desc: &Descriptor, _buffer: *const u8, _offset: u32, _size: u32) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn close(_desc: &mut Descriptor) {}
|
||||
|
||||
fn stat(desc: &Descriptor) -> Stat {
|
||||
let inode_result = Self::get_inode(desc.blockdev, desc.node);
|
||||
// This could be a little dangerous, but the descriptor should be checked in open().
|
||||
let inode = inode_result.unwrap();
|
||||
Stat { mode: inode.mode,
|
||||
size: inode.size,
|
||||
uid: inode.uid,
|
||||
gid: inode.gid, }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,2 +1,27 @@
|
||||
// test.rs
|
||||
|
||||
extern "C" {
|
||||
fn make_syscall(sysno: usize, arg0: usize, arg1: usize, arg2: usize, arg3: usize);
|
||||
}
|
||||
|
||||
pub fn test_block() {
|
||||
// Let's test the block driver!
|
||||
println!("Started test block process.");
|
||||
let desc = crate::fs::Descriptor {
|
||||
blockdev: 8,
|
||||
node: 1,
|
||||
loc: 0,
|
||||
size: 500,
|
||||
};
|
||||
let buffer = crate::kmem::kmalloc(1024);
|
||||
unsafe {
|
||||
make_syscall(63, 8, buffer as usize, 1024, 1024);
|
||||
for i in 0..32 {
|
||||
print!("{:02x} ", buffer.add(i).read());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
crate::kmem::kfree(buffer);
|
||||
println!("Test block finished");
|
||||
loop {}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user