From ad78dcc5a1b1097988935e290f5f62f5e451f2b4 Mon Sep 17 00:00:00 2001
From: 4lDO2 <4lDO2@protonmail.com>
Date: Fri, 30 Jun 2023 18:24:12 +0200
Subject: [PATCH] WIP: Rudimentary MAP_SHARED fmap implementation.

---
 src/context/context.rs | 35 +++++++++++++++++++++++++------
 src/context/memory.rs  | 38 ++++++++++++++++++++++++++++++----
 src/context/mod.rs     |  2 +-
 src/context/switch.rs  |  6 +++---
 src/scheme/mod.rs      |  4 ++++
 src/scheme/user.rs     | 47 ++++++++++++++++++++++++++++++++++++++----
 src/syscall/process.rs |  2 +-
 7 files changed, 115 insertions(+), 19 deletions(-)

diff --git a/src/context/context.rs b/src/context/context.rs
index bcd42f65..be2e8189 100644
--- a/src/context/context.rs
+++ b/src/context/context.rs
@@ -1,3 +1,4 @@
+use alloc::sync::Weak;
 use alloc::{
     boxed::Box,
     collections::VecDeque,
@@ -28,11 +29,13 @@ use crate::syscall::flag::{SIG_DFL, SigActionFlags};
 
 /// Unique identifier for a context (i.e. `pid`).
 use ::core::sync::atomic::AtomicUsize;
+
+use super::memory::FmapCtxt;
 int_like!(ContextId, AtomicContextId, usize, AtomicUsize);
 
 /// The status of a context - used for scheduling
 /// See `syscall::process::waitpid` and the `sync` module for examples of usage
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug)]
 pub enum Status {
     Runnable,
 
@@ -49,9 +52,19 @@ pub enum Status {
     Stopped(usize),
     Exited(usize),
 }
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+
+impl Status {
+    pub fn is_runnable(&self) -> bool {
+        matches!(self, Self::Runnable)
+    }
+    pub fn is_soft_blocked(&self) -> bool {
+        matches!(self, Self::Blocked)
+    }
+}
+
+#[derive(Clone, Debug)]
 pub enum HardBlockedReason {
-    AwaitingMmap,
+    AwaitingMmap { ctxt: Arc<FmapCtxt>, finished: Option<Frame> },
     // TODO: PageFaultOom?
     // TODO: NotYetStarted/ManuallyBlocked (when new contexts are created)
     // TODO: ptrace_stop?
@@ -174,7 +187,7 @@ impl ContextSnapshot {
             ens: context.ens,
             sigmask: context.sigmask,
             umask: context.umask,
-            status: context.status,
+            status: context.status.clone(),
             status_reason: context.status_reason,
             running: context.running,
             cpu_id: context.cpu_id,
@@ -329,7 +342,7 @@ impl Context {
 
     /// Block the context, and return true if it was runnable before being blocked
     pub fn block(&mut self, reason: &'static str) -> bool {
-        if self.status == Status::Runnable {
+        if self.status.is_runnable() {
             self.status = Status::Blocked;
             self.status_reason = reason;
             true
@@ -338,9 +351,19 @@ impl Context {
         }
     }
 
+    pub fn hard_block(&mut self, reason: HardBlockedReason) -> bool {
+        if self.status.is_runnable() {
+            self.status = Status::HardBlocked { reason };
+
+            true
+        } else {
+            false
+        }
+    }
+
     /// Unblock context, and return true if it was blocked before being marked runnable
     pub fn unblock(&mut self) -> bool {
-        if self.status == Status::Blocked {
+        if self.status.is_soft_blocked() {
             self.status = Status::Runnable;
             self.status_reason = "";
 
diff --git a/src/context/memory.rs b/src/context/memory.rs
index 3dd8140e..e00a2270 100644
--- a/src/context/memory.rs
+++ b/src/context/memory.rs
@@ -15,7 +15,9 @@ use crate::arch::paging::PAGE_SIZE;
 use crate::memory::{Enomem, Frame, get_page_info, PageInfo};
 use crate::paging::mapper::{Flusher, InactiveFlusher, PageFlushAll};
 use crate::paging::{KernelMapper, Page, PageFlags, PageMapper, RmmA, TableKind, VirtualAddress};
+use crate::scheme;
 
+use super::context::HardBlockedReason;
 use super::file::FileDescription;
 
 pub const MMAP_MIN_DEFAULT: usize = PAGE_SIZE;
@@ -1129,13 +1131,13 @@ pub unsafe fn copy_frame_to_frame_directly(dst: Frame, src: Frame) {
 }
 
 pub fn try_correcting_page_tables(faulting_page: Page, access: AccessMode) -> Result<(), PfError> {
-    let Ok(addr_space) = AddrSpace::current() else {
+    let Ok(addr_space_lock) = AddrSpace::current() else {
         log::debug!("User page fault without address space being set.");
         return Err(PfError::Segv);
     };
 
-    let mut addr_space = addr_space.write();
-    let addr_space = &mut *addr_space;
+    let mut addr_space_guard = addr_space_lock.write();
+    let mut addr_space = &mut *addr_space_guard;
 
     let Some((grant_base, grant_info)) = addr_space.grants.contains(faulting_page) else {
         log::debug!("Lacks grant");
@@ -1232,7 +1234,35 @@ pub fn try_correcting_page_tables(faulting_page: Page, access: AccessMode) -> Re
                 map_zeroed(&mut guard.table.utable, src_page, grant_flags, access == AccessMode::Write)?
             }
         }
-        Provider::FmapBorrowed { ref fmap } => todo!(),
+        Provider::FmapBorrowed { ref fmap } => {
+            let ctxt = Arc::clone(fmap);
+            let flags = map_flags(grant_info.flags());
+            drop(addr_space);
+
+            let (scheme_id, scheme_number) = match ctxt.file_ref.description.read() {
+                ref desc => (desc.scheme, desc.number),
+            };
+            let user_inner = scheme::schemes()
+                .get(scheme_id).and_then(|s| s.as_user_inner().transpose().ok().flatten())
+                .ok_or(PfError::Segv)?;
+
+            let offset = ctxt.file_ref.base_offset as u64 + (pages_from_grant_start * PAGE_SIZE) as u64;
+            user_inner.request_fmap(scheme_number, offset, 1, flags).unwrap();
+
+            let context_lock = super::current().map_err(|_| PfError::NonfatalInternalError)?;
+            context_lock.write().hard_block(HardBlockedReason::AwaitingMmap { ctxt, finished: None });
+
+            unsafe { super::switch(); }
+
+            let super::Status::HardBlocked { reason: HardBlockedReason::AwaitingMmap { finished: Some(frame), .. } } = core::mem::replace(&mut context_lock.write().status, super::Status::Runnable) else {
+                return Err(PfError::NonfatalInternalError);
+            };
+
+            addr_space_guard = addr_space_lock.write();
+            addr_space = &mut *addr_space_guard;
+
+            frame
+        }
     };
 
     if super::context_id().into() == 3 && debug {
diff --git a/src/context/mod.rs b/src/context/mod.rs
index e08bd71f..40e17f54 100644
--- a/src/context/mod.rs
+++ b/src/context/mod.rs
@@ -28,7 +28,7 @@ mod arch;
 mod arch;
 
 /// Context struct
-mod context;
+pub mod context;
 
 /// Context list
 mod list;
diff --git a/src/context/switch.rs b/src/context/switch.rs
index bca5f7ac..e5a13de4 100644
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -64,12 +64,12 @@ unsafe fn update_runnable(context: &mut Context, cpu_id: usize) -> bool {
     }
 
     // Unblock when there are pending signals
-    if context.status == Status::Blocked && !context.pending.is_empty() {
+    if context.status.is_soft_blocked() && !context.pending.is_empty() {
         context.unblock();
     }
 
     // Wake from sleep
-    if context.status == Status::Blocked && context.wake.is_some() {
+    if context.status.is_soft_blocked() && context.wake.is_some() {
         let wake = context.wake.expect("context::switch: wake not set");
 
         let current = time::monotonic();
@@ -80,7 +80,7 @@ unsafe fn update_runnable(context: &mut Context, cpu_id: usize) -> bool {
     }
 
     // Switch to context if it needs to run
-    context.status == Status::Runnable
+    context.status.is_runnable()
 }
 
 struct SwitchResult {
diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs
index e8b4ed1e..bda596f7 100644
--- a/src/scheme/mod.rs
+++ b/src/scheme/mod.rs
@@ -37,6 +37,7 @@ use self::root::RootScheme;
 use self::serio::SerioScheme;
 use self::sys::SysScheme;
 use self::time::TimeScheme;
+use self::user::UserInner;
 
 /// When compiled with the "acpi" feature - `acpi:` - allows drivers to read a limited set of ACPI tables.
 #[cfg(all(feature = "acpi", any(target_arch = "x86", target_arch = "x86_64")))]
@@ -335,6 +336,9 @@ pub trait KernelScheme: Scheme + Send + Sync + 'static {
     fn kfstatvfs(&self, id: usize, buf: UserSliceWo) -> Result<usize> {
         Err(Error::new(EBADF))
     }
+
+    // TODO: This demonstrates why we need to transition away from a dyn trait.
+    fn as_user_inner(&self) -> Option<Result<Arc<UserInner>>> { None }
 }
 
 #[derive(Debug)]
diff --git a/src/scheme/user.rs b/src/scheme/user.rs
index e0227f4c..9539d055 100644
--- a/src/scheme/user.rs
+++ b/src/scheme/user.rs
@@ -8,10 +8,12 @@ use core::{mem, usize};
 use core::convert::TryFrom;
 use spin::{Mutex, RwLock};
 
-use crate::context::{self, Context, BorrowedHtBuf};
+use crate::context::context::HardBlockedReason;
+use crate::context::{self, Context, BorrowedHtBuf, Status};
 use crate::context::file::FileDescription;
 use crate::context::memory::{AddrSpace, DANGLING, Grant, GrantFileRef, PageSpan, MmapMode, page_flags, FmapCtxt};
 use crate::event;
+use crate::memory::Frame;
 use crate::paging::{PAGE_SIZE, Page, VirtualAddress};
 use crate::scheme::{AtomicSchemeId, SchemeId};
 use crate::sync::{WaitQueue, WaitMap};
@@ -34,6 +36,7 @@ pub struct UserInner {
     context: Weak<RwLock<Context>>,
     todo: WaitQueue<Packet>,
     done: WaitMap<u64, Response>,
+    fmap: Mutex<BTreeMap<u64, Weak<RwLock<Context>>>>,
     unmounting: AtomicBool,
 }
 pub enum Response {
@@ -56,6 +59,7 @@ impl UserInner {
             todo: WaitQueue::new(),
             done: WaitMap::new(),
             unmounting: AtomicBool::new(false),
+            fmap: Mutex::new(BTreeMap::new()),
         }
     }
 
@@ -341,6 +345,21 @@ impl UserInner {
 
         Ok(packets_read * mem::size_of::<Packet>())
     }
+    pub fn request_fmap(&self, id: usize, offset: u64, required_page_count: usize, flags: MapFlags) -> Result<()> {
+        self.todo.send(Packet {
+            id: self.next_id(),
+            pid: context::context_id().into(),
+            a: KSMSG_MMAP,
+            b: id,
+            c: flags.bits(),
+            d: required_page_count,
+            uid: offset as u32,
+            gid: (offset >> 32) as u32,
+        });
+        event::trigger(self.root_id, self.handle_id, EVENT_READ);
+
+        Ok(())
+    }
     fn handle_packet(&self, packet: &Packet) -> Result<()> {
         if packet.id == 0 {
             // TODO: Simplify logic by using SKMSG with packet.id being ignored?
@@ -360,15 +379,31 @@ impl UserInner {
                     self.done.send(packet.id, Response::Fd(desc));
                 }
                 SKMSG_PROVIDE_MMAP => {
+                    log::info!("PROVIDE_MAP {:?}", packet);
                     let offset = u64::from(packet.uid) | (u64::from(packet.gid) << 32);
 
                     if offset % PAGE_SIZE as u64 != 0 {
-                        return Err(Error::new(EINVAL));
+                        return dbg!(Err(Error::new(EINVAL)));
+                    }
+
+                    let base_addr = VirtualAddress::new(packet.c);
+                    if base_addr.data() % PAGE_SIZE != 0 {
+                        return dbg!(Err(Error::new(EINVAL)));
                     }
 
-                    let page_count = packet.c;
+                    let page_count = packet.d;
+
+                    if page_count != 1 { return dbg!(Err(Error::new(EINVAL))); }
+                    let context = self.fmap.lock().remove(&packet.id).ok_or(Error::new(EINVAL))?.upgrade().ok_or(Error::new(ESRCH))?;
+
+                    let (frame, _) = AddrSpace::current()?.read().table.utable.translate(base_addr).ok_or(Error::new(EFAULT))?;
+
+                    let mut context = context.write();
+                    match context.status {
+                        Status::HardBlocked { reason: HardBlockedReason::AwaitingMmap { ref mut finished, .. } } => *finished = Some(Frame::containing_address(frame)),
+                        _ => (),
+                    }
 
-                    todo!("respond to SKMSG_PROVIDE_MMAP")
                 }
                 _ => return Err(Error::new(EINVAL)),
             }
@@ -752,6 +787,10 @@ impl KernelScheme for UserScheme {
 
         inner.fmap_inner(Arc::clone(addr_space), file, map)
     }
+
+    fn as_user_inner(&self) -> Option<Result<Arc<UserInner>>> {
+        Some(self.inner.upgrade().ok_or(Error::new(ENODEV)))
+    }
 }
 
 #[derive(PartialEq)]
diff --git a/src/syscall/process.rs b/src/syscall/process.rs
index 1284d317..4737b162 100644
--- a/src/syscall/process.rs
+++ b/src/syscall/process.rs
@@ -487,7 +487,7 @@ pub fn waitpid(pid: ContextId, status_ptr: Option<UserSliceWo>, flags: WaitFlags
                     println!("TODO: Hack for rustc - changing ppid of {} from {} to {}", context.id.into(), context.ppid.into(), ppid.into());
                     context.ppid = ppid;
                     //return Err(Error::new(ECHILD));
-                    Some(context.status)
+                    Some(context.status.clone())
                 } else {
                     None
                 }
-- 
GitLab