diff --git a/.gitignore b/.gitignore index 3c264194..a24ace96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables -/target/ +debug/ +target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/src/kxos-frame/Cargo.toml b/src/kxos-frame/Cargo.toml new file mode 100644 index 00000000..aa6b53fc --- /dev/null +++ b/src/kxos-frame/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "kxos-frame" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "1.3" \ No newline at end of file diff --git a/src/kxos-frame/src/cpu.rs b/src/kxos-frame/src/cpu.rs new file mode 100644 index 00000000..2c68e0e9 --- /dev/null +++ b/src/kxos-frame/src/cpu.rs @@ -0,0 +1,127 @@ +//! CPU. + +/// Defines a CPU-local variable. +#[macro_export] +macro_rules! cpu_local { + () => { + todo!() + }; +} + +/// Returns the number of CPUs. +pub fn num_cpus() -> u32 { + todo!() +} + +/// Returns the ID of this CPU. +pub fn this_cpu() -> u32 { + todo!() +} + +/// Cpu context, including both general-purpose registers and floating-point registers. +#[derive(Clone, Default)] +#[repr(C)] +pub struct CpuContext { + pub gp_regs: GpRegs, + pub fs_base: u64, + pub fp_regs: FpRegs, +} + +/// The general-purpose registers of CPU. +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct GpRegs { + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + pub rdi: u64, + pub rsi: u64, + pub rbp: u64, + pub rbx: u64, + pub rdx: u64, + pub rax: u64, + pub rcx: u64, + pub rsp: u64, + pub rip: u64, + pub rflag: u64, +} + +/// The floating-point state of CPU. +#[derive(Clone)] +#[repr(C)] +pub struct FpRegs { + //buf: Aligned, + is_valid: bool, +} + +impl FpRegs { + /// Create a new instance. + /// + /// Note that a newly-created instance's floating point state is not + /// initialized, thus considered invalid (i.e., `self.is_valid() == false`). + pub fn new() -> Self { + //let buf = Aligned(unsafe { MaybeUninit::uninit().assume_init() }); + //let is_valid = false; + //Self { buf, is_valid } + todo!("import aligned") + } + + /// Save CPU's current floating pointer states into this instance. + pub fn save(&mut self) { + // unsafe { + // _fxsave(self.buf.as_mut_ptr() as *mut u8); + // } + self.is_valid = true; + } + + /// Save the floating state given by a slice of u8. + /// + /// It is the caller's responsibility to ensure that the source slice contains + /// data that is in xsave/xrstor format. The slice must have a length of 512 bytes. + /// + /// After calling this method, the state of the instance will be considered valid. + pub unsafe fn save_from_slice(&mut self, src: &[u8]) { + //(&mut self.buf).copy_from_slice(src); + //self.is_valid = true; + } + + /// Returns whether the instance can contains data in valid xsave/xrstor format. + pub fn is_valid(&self) -> bool { + self.is_valid + } + + /// Clear the state of the instance. + /// + /// This method does not reset the underlying buffer that contains the floating + /// point state; it only marks the buffer __invalid__. + pub fn clear(&mut self) { + self.is_valid = false; + } + + /// Restore CPU's CPU floating pointer states from this instance. + /// + /// Panic. If the current state is invalid, the method will panic. + pub fn restore(&self) { + assert!(self.is_valid); + //unsafe { _fxrstor(self.buf.as_ptr()) }; + } + + /// Returns the floating point state as a slice. + /// + /// Note that the slice may contain garbage if `self.is_valid() == false`. + pub fn as_slice(&self) -> &[u8] { + //&*self.buf + todo!() + } +} + +impl Default for FpRegs { + fn default() -> Self { + Self::new() + } +} diff --git a/src/kxos-frame/src/device/io_port.rs b/src/kxos-frame/src/device/io_port.rs new file mode 100644 index 00000000..fc793137 --- /dev/null +++ b/src/kxos-frame/src/device/io_port.rs @@ -0,0 +1,38 @@ +use core::marker::PhantomData; + +use crate::prelude::*; + +/// An I/O port, representing a specific address in the I/O address of x86. +pub struct IoPort { + addr: u32, + _phantom: PhantomData, +} + +impl IoPort { + /// Create an I/O port. + /// + /// # Safety + /// + /// This function is marked unsafe as creating an I/O port is considered + /// a privileged operation. + pub unsafe fn new(addr: u32) -> Result { + todo!() + } +} + +impl IoPort { + /// Get the address of this I/O port. + pub fn addr(&self) -> u32 { + todo!() + } + + /// Read a value of `u32`. + pub fn read_u32(&self) -> u32 { + todo!() + } + + /// Write a value of `u32`. + pub fn write_u32(&self, val: u32) { + todo!() + } +} diff --git a/src/kxos-frame/src/device/irq.rs b/src/kxos-frame/src/device/irq.rs new file mode 100644 index 00000000..060f9e49 --- /dev/null +++ b/src/kxos-frame/src/device/irq.rs @@ -0,0 +1,46 @@ +use crate::prelude::*; + +/// An interupt request (IRQ) line. +pub struct IrqLine {} + +impl IrqLine { + /// Acquire an interrupt request line. + /// + /// # Safety + /// + /// This function is marked unsafe as manipulating interrupt lines is + /// considered a dangerous operation. + pub unsafe fn acquire(irq_num: u32) -> Arc { + todo!() + } + + /// Get the IRQ number. + pub fn num(&self) -> u32 { + todo!() + } + + /// Register a callback that will be invoked when the IRQ is active. + /// + /// A handle to the callback is returned. Dropping the handle + /// automatically unregisters the callback. + /// + /// For each IRQ line, multiple callbacks may be registered. + pub fn on_active(&self, callback: F) -> IrqCallbackHandle + where + F: Fn(&Self), + { + todo!() + } +} + +/// The handle to a registered callback for a IRQ line. +/// +/// When the handle is dropped, the callback will be unregistered automatically. +#[must_use] +pub struct IrqCallbackHandle {} + +impl Drop for IrqCallbackHandle { + fn drop(&mut self) { + todo!("unregister the callback") + } +} diff --git a/src/kxos-frame/src/device/mod.rs b/src/kxos-frame/src/device/mod.rs new file mode 100644 index 00000000..837af58e --- /dev/null +++ b/src/kxos-frame/src/device/mod.rs @@ -0,0 +1,7 @@ +//! Device-related APIs. + +mod io_port; +mod irq; + +pub use self::io_port::IoPort; +pub use self::irq::{IrqCallbackHandle, IrqLine}; diff --git a/src/kxos-frame/src/error.rs b/src/kxos-frame/src/error.rs new file mode 100644 index 00000000..0f4d9021 --- /dev/null +++ b/src/kxos-frame/src/error.rs @@ -0,0 +1,8 @@ +/// The error type which is returned from the APIs of this crate. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Error { + InvalidArgs, + NoMemory, + PageFault, + AccessDenied, +} diff --git a/src/kxos-frame/src/lib.rs b/src/kxos-frame/src/lib.rs new file mode 100644 index 00000000..f29bfefb --- /dev/null +++ b/src/kxos-frame/src/lib.rs @@ -0,0 +1,19 @@ +//! The framework part of KxOS. +#![no_std] +#![allow(dead_code)] +#![allow(unused_variables)] +#![feature(negative_impls)] + +extern crate alloc; + +pub mod cpu; +pub mod device; +mod error; +pub mod prelude; +pub mod task; +pub mod timer; +pub mod user; +mod util; +pub mod vm; + +pub use self::error::Error; diff --git a/src/kxos-frame/src/prelude.rs b/src/kxos-frame/src/prelude.rs new file mode 100644 index 00000000..bc93334a --- /dev/null +++ b/src/kxos-frame/src/prelude.rs @@ -0,0 +1,10 @@ +//! The prelude. + +pub type Result = core::result::Result; + +pub(crate) use alloc::boxed::Box; +pub(crate) use alloc::sync::Arc; +pub(crate) use alloc::vec::Vec; +pub(crate) use core::any::Any; + +pub use crate::vm::{Paddr, Vaddr}; diff --git a/src/kxos-frame/src/sync/mod.rs b/src/kxos-frame/src/sync/mod.rs new file mode 100644 index 00000000..a9887a14 --- /dev/null +++ b/src/kxos-frame/src/sync/mod.rs @@ -0,0 +1,5 @@ +mod spin; +mod wait; + +pub use self::spin::{SpinLock, SpinLockGuard}; +pub use self::wait::{WaitQueue}; \ No newline at end of file diff --git a/src/kxos-frame/src/sync/spin.rs b/src/kxos-frame/src/sync/spin.rs new file mode 100644 index 00000000..e0638add --- /dev/null +++ b/src/kxos-frame/src/sync/spin.rs @@ -0,0 +1,39 @@ +/// A spin lock. +pub struct SpinLock { + val: T, +} + +impl SpinLock { + /// Creates a new spin lock. + pub fn new(val: T) -> Self { + todo!() + } + + /// Acquire the spin lock. + /// + /// This method runs in a busy loop until the lock can be acquired. + /// After acquiring the spin lock, all interrupts are disabled. + pub fn lock(&self) -> SpinLockGuard<'a> { + todo!() + } +} + +unsafe impl Send for SpinLock {} +unsafe impl Sync for SpinLock {} + +/// The guard of a spin lock. +pub struct SpinLockGuard<'a, T: ?Sized + 'a> { + lock: &'a SpinLock +} + +impl<'a, T> Deref for SpinLockGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + todo!() + } +} + +impl<'a, T: ?Sized> !Send for SpinLockGuard<'a, T> {} + +unsafe impl Sync for SpinLockGuard<'_, T> {} \ No newline at end of file diff --git a/src/kxos-frame/src/sync/wait.rs b/src/kxos-frame/src/sync/wait.rs new file mode 100644 index 00000000..37597c19 --- /dev/null +++ b/src/kxos-frame/src/sync/wait.rs @@ -0,0 +1,69 @@ +/// A wait queue. +/// +/// One may wait on a wait queue to put its executing thread to sleep. +/// Multiple threads may be the waiters of a wait queue. +/// Other threads may invoke the `wake`-family methods of a wait queue to +/// wake up one or many waiter threads. +pub struct WaitQueue {} + +impl WaitQueue { + /// Creates a new instance. + pub fn new() -> Self { + todo!() + } + + /// Wait until some condition becomes true. + /// + /// This method takes a closure that tests a user-given condition. + /// The method only returns if the condition becomes true. + /// A waker thread should first make the condition true, then invoke the + /// `wake`-family method. This ordering is important to ensure that waiter + /// threads do not lose any wakeup notifiations. + /// + /// By taking a condition closure, this wait-wakeup mechanism becomes + /// more efficient and robust. + pub fn wait_until(&self, mut cond: F) + where + F: FnMut() -> bool, + { + let waiter = Waiter::new(); + self.enqueue(&waiter); + loop { + if (cond)() { + self.dequeue(&waiter); + return; + } + waiter.wait(); + } + self.dequeue(&waiter); + } + + /// Wake one waiter thread, if there is one. + pub fn wake_one(&self) { + todo!() + } + + /// Wake all waiter threads. + pub fn wake_all(&self) { + todo!() + } + + fn enqueue(&self, waiter: &Waiter) { + todo!() + } + fn dequeue(&self, waiter: &Waiter) { + todo!() + } +} + +struct Waiter {} + +impl Waiter { + pub fn new() -> Self { + todo!() + } + + pub fn wait(&self) { + todo!() + } +} diff --git a/src/kxos-frame/src/task/mod.rs b/src/kxos-frame/src/task/mod.rs new file mode 100644 index 00000000..9b93c2ce --- /dev/null +++ b/src/kxos-frame/src/task/mod.rs @@ -0,0 +1,7 @@ +//! Tasks are the unit of code execution. + +mod scheduler; +mod task; + +pub use self::scheduler::{set_scheduler, Scheduler}; +pub use self::task::{Task, TaskStatus}; diff --git a/src/kxos-frame/src/task/scheduler.rs b/src/kxos-frame/src/task/scheduler.rs new file mode 100644 index 00000000..b40c7522 --- /dev/null +++ b/src/kxos-frame/src/task/scheduler.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; +use crate::task::Task; + +/// A scheduler for tasks. +/// +/// An implementation of scheduler can attach scheduler-related information +/// with the `TypeMap` returned from `task.data()`. +pub trait Scheduler { + fn enqueue(&self, task: Arc); + + fn dequeue(&self) -> Option>; +} + +/// Set the global task scheduler. +/// +/// This must be called before invoking `Task::spawn`. +pub fn set_scheduler(scheduler: &'static dyn Scheduler) { + todo!() +} diff --git a/src/kxos-frame/src/task/task.rs b/src/kxos-frame/src/task/task.rs new file mode 100644 index 00000000..ef4e30ca --- /dev/null +++ b/src/kxos-frame/src/task/task.rs @@ -0,0 +1,66 @@ +use crate::prelude::*; +use crate::user::UserSpace; + +/// A task that executes a function to the end. +pub struct Task { + func: Box, + data: Box, + user_space: Option, +} + +impl Task { + /// Gets the current task. + pub fn current() -> Arc { + todo!() + } + + /// Yields execution so that another task may be scheduled. + /// + /// Note that this method cannot be simply named "yield" as the name is + /// a Rust keyword. + pub fn yield_now() { + todo!() + } + + /// Spawns a task that executes a function. + /// + /// Each task is associated with a per-task data and an optional user space. + /// If having a user space, then the task can switch to the user space to + /// execute user code. Multiple tasks can share a single user space. + pub fn spawn( + task_fn: F, + task_data: T, + user_space: Option>, + ) -> Result> + where + F: FnOnce(), + T: Any + Send + Sync, + { + todo!() + } + + /// Returns the task status. + pub fn status(&self) -> TaskStatus { + todo!() + } + + /// Returns the task data. + pub fn data(&self) -> &dyn Any { + todo!() + } + + /// Returns the user space of this task, if it has. + pub fn user_space(&self) -> Option<&Arc> { + todo!() + } +} + +/// The status of a task. +pub enum TaskStatus { + /// The task is runnable. + Runnable, + /// The task is sleeping. + Sleeping, + /// The task has exited. + Exited, +} diff --git a/src/kxos-frame/src/timer.rs b/src/kxos-frame/src/timer.rs new file mode 100644 index 00000000..9fa3d499 --- /dev/null +++ b/src/kxos-frame/src/timer.rs @@ -0,0 +1,43 @@ +//! Timer. + +use crate::prelude::*; +use core::time::Duration; + +/// A timer invokes a callback function after a specified span of time elapsed. +/// +/// A new timer is initially inactive. Only after a timeout value is set with +/// the `set` method can the timer become active and the callback function +/// be triggered. +/// +/// Timers are one-shot. If the time is out, one has to set the timer again +/// in order to trigger the callback again. +pub struct Timer {} + +impl Timer { + /// Creates a new instance, given a callback function. + pub fn new(f: F) -> Result + where + F: FnMut(&Self), + { + todo!() + } + + /// Set a timeout value. + /// + /// If a timeout value is already set, the timeout value will be refreshed. + pub fn set(&self, timeout: Duration) { + todo!() + } + + /// Returns the remaining timeout value. + /// + /// If the timer is not set, then the remaining timeout value is zero. + pub fn remain(&self) -> Duration { + todo!() + } + + /// Clear the timeout value. + pub fn clear(&self) { + todo!() + } +} diff --git a/src/kxos-frame/src/user.rs b/src/kxos-frame/src/user.rs new file mode 100644 index 00000000..325fc574 --- /dev/null +++ b/src/kxos-frame/src/user.rs @@ -0,0 +1,105 @@ +//! User space. + +use crate::cpu::CpuContext; +use crate::prelude::*; +use crate::task::Task; +use crate::vm::VmSpace; + +/// A user space. +/// +/// Each user space has a VM address space and allows a task to execute in +/// user mode. +pub struct UserSpace {} + +impl UserSpace { + /// Creates a new instance. + /// + /// Each instance maintains a VM address space and the CPU state to enable + /// execution in the user space. + pub fn new(vm_space: VmSpace, cpu_ctx: CpuContext) -> Self { + todo!() + } + + /// Returns the VM address space. + pub fn vm_space(&self) -> &VmSpace { + todo!() + } + + /// Returns the user mode that is bound to the current task and user space. + /// + /// See `UserMode` on how to use it to execute user code. + /// + /// # Panic + /// + /// This method is intended to only allow each task to have at most one + /// instance of `UserMode` initiated. If this method is called again before + /// the first instance for the current task is dropped, then the method + /// panics. + pub fn user_mode(&self) -> UserMode<'_> { + todo!() + } +} + +/// Code execution in the user mode. +/// +/// This type enables executing the code in user space from a task in the kernel +/// space safely. +/// +/// Here is a sample code on how to use `UserMode`. +/// +/// ``` +/// let current = Task::current(); +/// let user_space = current.user_space() +/// .expect("the current task is associated with a user space"); +/// let mut user_mode = user_space.user_mode(); +/// loop { +/// // Execute in the user space until some interesting user event occurs +/// let user_event = user_mode.execute(); +/// todo!("handle the user event, e.g., syscall"); +/// } +/// ``` +pub struct UserMode<'a> { + current: Arc, + user_space: &'a Arc, +} + +// An instance of `UserMode` is bound to the current task. So it cannot be +impl<'a> !Send for UserMode<'a> {} + +impl<'a> UserMode<'a> { + /// Starts executing in the user mode. + /// + /// The method returns for one of three possible reasons indicated by `UserEvent`. + /// 1. The user invokes a system call; + /// 2. The user triggers an exception; + /// 3. The user triggers a fault. + /// + /// After handling the user event and updating the user-mode CPU context, + /// this method can be invoked again to go back to the user space. + pub fn execute(&mut self) -> UserEvent { + todo!() + } + + /// Returns an immutable reference the user-mode CPU context. + pub fn context(&self) -> &CpuContext { + todo!() + } + + /// Returns a mutable reference the user-mode CPU context. + pub fn context_mut(&mut self) -> &mut CpuContext { + todo!() + } +} + +/// A user event is what brings back the control of the CPU back from +/// the user space to the kernel space. +/// +/// Note that hardware interrupts are not considered user events as they +/// are triggered by devices and not visible to user programs. +/// To handle interrupts, one should register callback funtions for +/// IRQ lines (`IrqLine`). +pub enum UserEvent { + Syscall, + Exception, + Fault, +} diff --git a/src/kxos-frame/src/util/mod.rs b/src/kxos-frame/src/util/mod.rs new file mode 100644 index 00000000..2c7c6017 --- /dev/null +++ b/src/kxos-frame/src/util/mod.rs @@ -0,0 +1,3 @@ +mod type_map; + +pub use self::type_map::TypeMap; diff --git a/src/kxos-frame/src/util/type_map.rs b/src/kxos-frame/src/util/type_map.rs new file mode 100644 index 00000000..e998fb5f --- /dev/null +++ b/src/kxos-frame/src/util/type_map.rs @@ -0,0 +1,26 @@ +/// A type map is a collection whose keys are types, rather than values. +pub struct TypeMap {} + +pub trait Any: core::any::Any + Send + Sync {} + +impl TypeMap { + /// Creates an empty typed map. + pub fn new() -> Self { + todo!() + } + + /// Inserts a new item of type `T`. + pub fn insert(&mut self, val: T) -> Option { + todo!() + } + + /// Gets an item of type `T`. + pub fn get(&self) -> Option<&T> { + todo!() + } + + /// Gets an item of type `T`. + pub fn remove(&self) -> Option { + todo!() + } +} diff --git a/src/kxos-frame/src/vm/frame.rs b/src/kxos-frame/src/vm/frame.rs new file mode 100644 index 00000000..2e207870 --- /dev/null +++ b/src/kxos-frame/src/vm/frame.rs @@ -0,0 +1,172 @@ +use core::iter::Iterator; + +use crate::prelude::*; + +/// A collection of page frames (physical memory pages). +/// +/// For the most parts, `VmFrameVec` is like `Vec`. But the +/// implementation may or may not be based on `Vec`. Having a dedicated +/// type to represent a series of page frames is convenient because, +/// more often than not, one needs to operate on a batch of frames rather +/// a single frame. +pub struct VmFrameVec(Vec); + +impl VmFrameVec { + /// Allocate a collection of free frames according to the given options. + /// + /// All returned frames are safe to use in the sense that they are + /// not _typed memory_. We define typed memory as the memory that + /// may store Rust objects or affect Rust memory safety, e.g., + /// the code and data segments of the OS kernel, the stack and heap + /// allocated for the OS kernel. + /// + /// For more information, see `VmAllocOptions`. + pub fn allocate(options: &VmAllocOptions) -> Result { + todo!() + } + + /// Pushs a new frame to the collection. + pub fn push(&mut self, new_frame: VmFrame) { + todo!() + } + + /// Pop a frame from the collection. + pub fn pop(&mut self) -> Option { + todo!() + } + + /// Removes a frame at a position. + pub fn remove(&mut self, at: usize) -> VmFrame { + todo!() + } + + /// Append some frames. + pub fn append(&mut self, more: VmFrameVec) -> Result<()> { + todo!() + } + + /// Truncate some frames. + /// + /// If `new_len >= self.len()`, then this method has no effect. + pub fn truncate(&mut self, new_len: usize) { + todo!() + } + + /// Returns an iterator + pub fn iter(&self) -> VmFrameVecIter<'_> { + todo!() + } + + /// Returns the number of frames. + pub fn len(&self) -> usize { + todo!() + } + + /// Returns the number of bytes. + /// + /// This method is equivalent to `self.len() * PAGE_SIZE`. + pub fn nbytes(&self) -> usize { + todo!() + } +} + +/// An iterator for frames. +pub struct VmFrameVecIter<'a> { + frames: &'a VmFrameVec, + // more... +} + +impl<'a> Iterator for VmFrameVecIter<'a> { + type Item = &'a VmFrame; + + fn next(&mut self) -> Option { + todo!() + } +} + +/// Options for allocating physical memory pages (or frames). +/// See `VmFrameVec::alloc`. +pub struct VmAllocOptions {} + +impl VmAllocOptions { + /// Creates new options for allocating the specified number of frames. + pub fn new(len: usize) -> Self { + todo!() + } + + /// Sets the physical address of the first frame. + /// + /// If the physical address is given, then the allocated frames will be + /// contiguous. + /// + /// The default value is `None`. + pub fn paddr(&mut self, paddr: Option) -> &mut Self { + todo!() + } + + /// Sets whether the allocated frames should be contiguous. + /// + /// If the physical address is set, then the frames must be contiguous. + /// + /// The default value is `false`. + pub fn is_contiguous(&mut self, is_contiguous: bool) -> &mut Self { + todo!() + } + + /// Sets whether the pages can be accessed by devices through + /// Direct Memory Access (DMA). + /// + /// In a TEE environment, DMAable pages are untrusted pages shared with + /// the VMM. + pub fn can_dma(&mut self, can_dma: bool) -> &mut Self { + todo!() + } +} + +/// A handle to a page frame. +/// +/// An instance of `VmFrame` is a handle to a page frame (a physical memory +/// page). A cloned `VmFrame` refers to the same page frame as the original. +/// As the original and cloned instances point to the same physical address, +/// they are treated as equal to each other. Behind the scene, +/// a reference counter is maintained for each page frame so that +/// when all instances of `VmFrame` that refer to the +/// same page frame are dropped, the page frame will be freed. +/// Free page frames are allocated in bulk by `VmFrameVec::allocate`. +pub struct VmFrame {} + +impl VmFrame { + /// Creates a new VmFrame. + /// + /// # Safety + /// + /// The given physical address must be valid for use. + pub(crate) unsafe fn new(paddr: Paddr) -> Self { + todo!() + } + + /// Returns the physical address of the page frame. + pub fn paddr(&self) -> Paddr { + todo!() + } + + /// Returns whether the page frame is accessible by DMA. + /// + /// In a TEE environment, DMAable pages are untrusted pages shared with + /// the VMM. + pub fn can_dma(&self) -> bool { + todo!() + } +} + +impl Clone for VmFrame { + fn clone(&self) -> Self { + todo!("inc ref cnt") + } +} + +impl Drop for VmFrame { + fn drop(&mut self) { + todo!("dec ref cnt and if zero, free the page frame") + } +} diff --git a/src/kxos-frame/src/vm/io.rs b/src/kxos-frame/src/vm/io.rs new file mode 100644 index 00000000..c447d3a7 --- /dev/null +++ b/src/kxos-frame/src/vm/io.rs @@ -0,0 +1,64 @@ +use crate::prelude::*; +use crate::vm::Pod; + +/// A trait that enables reading/writing data from/to a VM object, +/// e.g., `VmSpace`, `VmFrameVec`, and `VmFrame`. +/// +/// # Concurrency +/// +/// The methods may be executed by multiple concurrent reader and writer +/// threads. In this case, if the results of concurrent reads or writes +/// desire predictability or atomicity, the users should add extra mechanism +/// for such properties. +pub trait VmIo: Send + Sync { + /// Read a specified number of bytes at a specified offset into a given buffer. + /// + /// # No short reads + /// + /// On success, the output `buf` must be filled with the requested data + /// completely. If, for any reason, the requested data is only partially + /// available, then the method shall return an error. + fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()>; + + /// Read a value of a specified type at a specified offset. + fn read_val(&self, offset: usize) -> Result { + let mut val = T::new_uninit(); + self.read_bytes(offset, val.as_bytes_mut())?; + Ok(val) + } + + /// Read a slice of a specified type at a specified offset. + /// + /// # No short reads + /// + /// Similar to `read_bytes`. + fn read_slice(&self, offset: usize, slice: &mut [T]) -> Result<()> { + let buf = unsafe { core::mem::transmute(slice) }; + self.read_bytes(offset, buf) + } + + /// Write a specified number of bytes from a given buffer at a specified offset. + /// + /// # No short writes + /// + /// On success, the input `buf` must be written to the VM object entirely. + /// If, for any reason, the input data can only be written partially, + /// then the method shall return an error. + fn write_bytes(&self, offset: usize, buf: &[u8]) -> Result<()>; + + /// Write a value of a specified type at a specified offset. + fn write_val(&self, offset: usize, new_val: &T) -> Result<()> { + self.write_bytes(offset, new_val.as_bytes())?; + Ok(()) + } + + /// Write a slice of a specified type at a specified offset. + /// + /// # No short write + /// + /// Similar to `write_bytes`. + fn write_slice(&self, offset: usize, slice: &[T]) -> Result<()> { + let buf = unsafe { core::mem::transmute(slice) }; + self.write_bytes(offset, buf) + } +} diff --git a/src/kxos-frame/src/vm/mod.rs b/src/kxos-frame/src/vm/mod.rs new file mode 100644 index 00000000..7a0362c7 --- /dev/null +++ b/src/kxos-frame/src/vm/mod.rs @@ -0,0 +1,20 @@ +//! Virtual memory (VM). + +/// Virtual addresses. +pub type Vaddr = usize; + +/// Physical addresses. +pub type Paddr = usize; + +/// The size of a VM page or a page frame. +pub const PAGE_SIZE: usize = 0x1000; // 4KB + +mod frame; +mod io; +mod pod; +mod space; + +pub use self::frame::{VmAllocOptions, VmFrame, VmFrameVec, VmFrameVecIter}; +pub use self::io::VmIo; +pub use self::pod::Pod; +pub use self::space::{VmMapOptions, VmPerm, VmSpace}; diff --git a/src/kxos-frame/src/vm/pod.rs b/src/kxos-frame/src/vm/pod.rs new file mode 100644 index 00000000..d65826fa --- /dev/null +++ b/src/kxos-frame/src/vm/pod.rs @@ -0,0 +1,60 @@ +use core::mem::MaybeUninit; + +/// A marker trait for plain old data (POD). +/// +/// A POD type `T:Pod` supports converting to and from arbitrary +/// `mem::size_of::()` bytes _safely_. +/// For example, simple primitive types like `u8` and `i16` +/// are POD types. But perhaps surprisingly, `bool` is not POD +/// because Rust compiler makes implicit assumption that +/// a byte of `bool` has a value of either `0` or `1`. +/// Interpreting a byte of value `3` has a `bool` value has +/// undefined behavior. +/// +/// # Safety +/// +/// Marking a non-POD type as POD may cause undefined behaviors. +pub unsafe trait Pod: Copy + Sized { + /// Creates a new instance of Pod type that is filled with zeroes. + fn new_zeroed() -> Self { + // SAFETY. An all-zero value of `T: Pod` is always valid. + unsafe { core::mem::zeroed() } + } + + /// Creates a new instance of Pod type with uninitialized content. + fn new_uninit() -> Self { + // SAFETY. A value of `T: Pod` can have arbitrary bits. + unsafe { MaybeUninit::uninit().assume_init() } + } + + /// Creates a new instance from the given bytes. + fn from_bytes(bytes: &[u8]) -> Self { + let mut new_self = Self::new_uninit(); + new_self.as_bytes_mut().copy_from_slice(bytes); + new_self + } + + /// As a slice of bytes. + fn as_bytes(&self) -> &[u8] { + let ptr = self as *const Self as *const u8; + let len = core::mem::size_of::(); + unsafe { core::slice::from_raw_parts(ptr, len) } + } + + /// As a mutable slice of bytes. + fn as_bytes_mut(&mut self) -> &mut [u8] { + let ptr = self as *mut Self as *mut u8; + let len = core::mem::size_of::(); + unsafe { core::slice::from_raw_parts_mut(ptr, len) } + } +} + +macro_rules! impl_pod_for { + ($($token:tt)*/* define the input */) => { + /* define the expansion */ + }; +} + +impl_pod_for!(u8, u16, u32, u64, i8, i16, i32, i64,); + +//unsafe impl [T; N] for Pod {} diff --git a/src/kxos-frame/src/vm/space.rs b/src/kxos-frame/src/vm/space.rs new file mode 100644 index 00000000..f7d99bee --- /dev/null +++ b/src/kxos-frame/src/vm/space.rs @@ -0,0 +1,112 @@ +use bitflags::bitflags; +use core::ops::Range; + +use crate::prelude::*; +use crate::vm::VmFrameVec; + +/// Virtual memory space. +/// +/// A virtual memory space (`VmSpace`) can be created and assigned to a user space so that +/// the virtual memory of the user space can be manipulated safely. For example, +/// given an arbitrary user-space pointer, one can read and write the memory +/// location refered to by the user-space pointer without the risk of breaking the +/// memory safety of the kernel space. +/// +/// A newly-created `VmSpace` is not backed by any physical memory pages. +/// To provide memory pages for a `VmSpace`, one can allocate and map +/// physical memory (`VmFrames`) to the `VmSpace`. +pub struct VmSpace {} + +impl VmSpace { + /// Creates a new VM address space. + pub fn new() -> Self { + todo!() + } + + /// Maps some physical memory pages into the VM space according to the given + /// options, returning the address where the mapping is created. + /// + /// For more information, see `VmMapOptions`. + pub fn map(&self, frames: VmFrameVec, options: &VmMapOptions) -> Result { + todo!() + } + + /// Unmaps the physical memory pages within the VM address range. + /// + /// The range is allowed to contain gaps, where no physical memory pages + /// are mapped. + pub fn unmap(&self, range: &Range) -> Result<()> { + todo!() + } + + /// Update the VM protection permissions within the VM address range. + /// + /// The entire specified VM range must have been mapped with physical + /// memory pages. + pub fn protect(&self, range: &Range, perm: VmPerm) -> Result<()> { + todo!() + } +} + +/// Options for mapping physical memory pages into a VM address space. +/// See `VmSpace::map`. +pub struct VmMapOptions {} + +impl VmMapOptions { + /// Creates the default options. + pub fn new() -> Self { + todo!() + } + + /// Sets the alignment of the address of the mapping. + /// + /// The alignment must be a power-of-2 and greater than or equal to the + /// page size. + /// + /// The default value of this option is the page size. + pub fn align(&mut self, align: usize) -> &mut Self { + todo!() + } + + /// Sets the permissions of the mapping, which affects whether + /// the mapping can be read, written, or executed. + /// + /// The default value of this option is read-only. + pub fn perm(&mut self, perm: VmPerm) -> &mut Self { + todo!() + } + + /// Sets the address of the new mapping. + /// + /// The default value of this option is `None`. + pub fn addr(&mut self, addr: Option) -> &mut Self { + todo!() + } + + /// Sets whether the mapping can overwrite any existing mappings. + /// + /// If this option is `true`, then the address option must be `Some(_)`. + /// + /// The default value of this option is `false`. + pub fn can_overwrite(&mut self, can_overwrite: bool) -> &mut Self { + todo!() + } +} + +bitflags! { + /// Virtual memory protection permissions. + pub struct VmPerm: u8 { + /// Readable. + const R = 0b00000001; + /// Writable. + const W = 0b00000010; + /// Executable. + const X = 0b00000100; + /// Readable + writable. + const RW = Self::R.bits | Self::W.bits; + /// Readable + execuable. + const RX = Self::R.bits | Self::X.bits; + /// Readable + writable + executable. + const RWX = Self::R.bits | Self::W.bits | Self::X.bits; + } +} diff --git a/src/rust-toolchain.toml b/src/rust-toolchain.toml new file mode 100644 index 00000000..725abbe3 --- /dev/null +++ b/src/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2022-08-01" \ No newline at end of file