2024-05-13 06:21:53 +00:00
|
|
|
// SPDX-FileCopyrightText: 2024 bunnie <bunnie@kosagi.com>
|
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
2024-05-17 16:28:34 +00:00
|
|
|
use core::cmp::Ordering;
|
|
|
|
|
|
2024-05-13 06:15:38 +00:00
|
|
|
use loader::swap::SWAP_FLG_WIRED;
|
2024-05-13 05:59:17 +00:00
|
|
|
use loader::swap::SWAP_RPT_VADDR;
|
remember tid for swap-read; do PID dance for resume abstraction
We want to use the finish_callback_and_resume() function because
it's well-tested and sets up everything just right, but it expects
that we're coming from another process. Unfortunately, in the
middle of all of this, we need to map a page back into the
destination process' memory space, so we end up starting from
the swapper's memory space, changing into the destination process'
memory space, then mapping the page, then changing back into
the swapper's memory space, then using the finish_callback_and_resume()
call.
I think the impact might not be *that* bad, mainly it's a TLB
miss and the PTW isn't super bad, and this comes after a disastrously
expensive decrypt-from-RAM operation -- so probably not worth it
to angonize over optimizing this bit at this point, and stick
with well-tested primitives.
2024-05-03 17:54:23 +00:00
|
|
|
use xous_kernel::SWAPPER_PID;
|
2024-05-13 05:59:17 +00:00
|
|
|
use xous_kernel::{SysCallResult, PID, SID, TID};
|
2024-03-07 20:14:43 +00:00
|
|
|
|
|
|
|
|
use crate::arch::current_pid;
|
2024-03-08 10:31:39 +00:00
|
|
|
use crate::arch::mem::MMUFlags;
|
2024-05-24 16:06:38 +00:00
|
|
|
use crate::arch::mem::EXCEPTION_STACK_TOP;
|
2024-04-01 06:05:26 +00:00
|
|
|
use crate::arch::mem::PAGE_SIZE;
|
2024-03-07 20:14:43 +00:00
|
|
|
use crate::mem::MemoryManager;
|
|
|
|
|
use crate::services::SystemServices;
|
|
|
|
|
|
2024-05-24 18:10:55 +00:00
|
|
|
/// This might change depending on the target, link options, and version of LLVM.
|
|
|
|
|
/// The good news is it's static with every version, and constant after the first
|
|
|
|
|
/// invocation. So, if it works once, it should keep on working for that particular
|
|
|
|
|
/// combination of target/link options/compiler version.
|
2024-05-27 18:41:08 +00:00
|
|
|
#[cfg(any(feature = "debug-swap", feature = "debug-swap-verbose"))] // more stack needed for debug
|
|
|
|
|
const BACKUP_STACK_SIZE_WORDS: usize = 2048 / core::mem::size_of::<usize>();
|
|
|
|
|
#[cfg(not(any(feature = "debug-swap", feature = "debug-swap-verbose")))]
|
2024-05-24 17:34:11 +00:00
|
|
|
const BACKUP_STACK_SIZE_WORDS: usize = 1024 / core::mem::size_of::<usize>();
|
|
|
|
|
|
2024-05-13 09:24:35 +00:00
|
|
|
/// userspace swapper -> kernel ABI (see kernel/src/syscall.rs)
|
|
|
|
|
/// This ABI is copy-paste synchronized with what's in the userspace handler. It's left out of
|
|
|
|
|
/// xous-rs so that we can change it without having to push crates to crates.io.
|
|
|
|
|
/// Since there is only one place the ABI could be used, we're going to stick with
|
|
|
|
|
/// this primitive method of synchronization because it reduces the activation barrier
|
|
|
|
|
/// to fix bugs.
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
|
|
|
pub enum SwapAbi {
|
|
|
|
|
Invalid = 0,
|
2024-05-24 18:10:55 +00:00
|
|
|
ClearMemoryNow = 1,
|
2024-05-13 11:20:59 +00:00
|
|
|
GetFreePages = 2,
|
2024-05-27 18:41:08 +00:00
|
|
|
RetrievePage = 3,
|
2024-05-24 16:06:38 +00:00
|
|
|
HardOom = 4,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
StealPage = 5,
|
|
|
|
|
ReleaseMemory = 6,
|
2024-05-13 09:24:35 +00:00
|
|
|
}
|
|
|
|
|
/// SYNC WITH `xous-swapper/src/main.rs`
|
|
|
|
|
impl SwapAbi {
|
|
|
|
|
pub fn from(val: usize) -> SwapAbi {
|
|
|
|
|
use SwapAbi::*;
|
|
|
|
|
match val {
|
2024-05-24 18:10:55 +00:00
|
|
|
1 => ClearMemoryNow,
|
2024-05-13 11:20:59 +00:00
|
|
|
2 => GetFreePages,
|
2024-05-27 18:41:08 +00:00
|
|
|
3 => RetrievePage,
|
2024-05-24 16:06:38 +00:00
|
|
|
4 => HardOom,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
5 => StealPage,
|
|
|
|
|
6 => ReleaseMemory,
|
2024-05-13 09:24:35 +00:00
|
|
|
_ => Invalid,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// kernel -> swapper handler ABI
|
|
|
|
|
/// This is the response ABI from the kernel into the swapper. It also tracks intermediate state
|
|
|
|
|
/// necessary for proper return from these calls.
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
|
pub enum BlockingSwapOp {
|
|
|
|
|
/// PID of the target block, current paddr of block, original vaddr in the space of target block PID,
|
2024-05-14 18:44:11 +00:00
|
|
|
/// physical address of block. Returns to PID of the target block.
|
2024-05-13 09:24:35 +00:00
|
|
|
ReadFromSwap(PID, TID, usize, usize, usize),
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
/// Immediate OOM. Drop everything and try to recover; from here until exit, everything runs in
|
|
|
|
|
/// an un-interruptable context, no progress allowed. This currently can only originate from one
|
|
|
|
|
/// location, if we need multi-location origin then we have to also track the re-entry point in the
|
|
|
|
|
/// kernel memory cycle. Arguments are the TID/PID of the context running that triggered the HardOom,
|
|
|
|
|
/// as well as the virtual address that triggered the hard OOM.
|
2024-05-24 16:06:38 +00:00
|
|
|
HardOomSyscall(TID, PID),
|
2024-05-13 09:24:35 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-13 06:21:53 +00:00
|
|
|
/// This structure is a copy of what's defined in the loader's swap module. The reason
|
|
|
|
|
/// we can't condense the two APIs is because PID is actually defined differently in the
|
|
|
|
|
/// kernel than in the loader: a PID for the loader is a `u8`. A PID for the kernel is a
|
|
|
|
|
/// NonZeroU8. Thus the APIs are not compatible, and we have to maintain two sets of
|
|
|
|
|
/// calls.
|
|
|
|
|
///
|
|
|
|
|
/// Also, critically, `SwapAlloc` in the kernel needs an `update` and `reparent` call,
|
|
|
|
|
/// which increments `next_epoch` -- these can't be migrated into the swapper, and Rust
|
|
|
|
|
/// does not allow extensions to types that aren't native to your crate. So, we're left
|
|
|
|
|
/// with redundant copies of the structure definition.
|
2024-05-13 13:00:17 +00:00
|
|
|
#[derive(Debug)]
|
2024-05-15 19:55:41 +00:00
|
|
|
#[repr(C)]
|
2024-05-13 06:15:38 +00:00
|
|
|
pub struct SwapAlloc {
|
|
|
|
|
timestamp: u32,
|
|
|
|
|
/// virtual_page_number[19:0] | flags[3:0] | pid[7:0]
|
|
|
|
|
vpn: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SwapAlloc {
|
|
|
|
|
pub fn is_pid(&self, pid: PID) -> bool { self.vpn as u8 == pid.get() }
|
|
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
pub fn is_some(&self) -> bool { self.vpn as u8 != 0 }
|
|
|
|
|
|
|
|
|
|
pub fn is_none(&self) -> bool { self.vpn as u8 == 0 }
|
|
|
|
|
|
|
|
|
|
pub fn get_pid(&self) -> Option<PID> {
|
2024-05-14 18:44:11 +00:00
|
|
|
if (self.vpn & 0xFF) as u8 == 0 { None } else { Some(PID::new(self.vpn as u8).unwrap()) }
|
2024-05-13 06:15:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub unsafe fn update(&mut self, pid: Option<PID>, vaddr: Option<usize>) {
|
|
|
|
|
crate::swap::Swap::with_mut(|s| {
|
|
|
|
|
self.timestamp = s.next_epoch();
|
|
|
|
|
if let Some(pid) = pid {
|
|
|
|
|
s.track_alloc(true);
|
|
|
|
|
if let Some(va) = vaddr {
|
2024-05-27 18:41:08 +00:00
|
|
|
#[cfg(feature = "debug-swap-verbose")]
|
2024-05-15 19:55:41 +00:00
|
|
|
println!(
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
"-- update {}/{:x} <- {}/{:x} @ {:x} (in pid{})",
|
2024-05-15 19:55:41 +00:00
|
|
|
pid.get(),
|
|
|
|
|
va,
|
|
|
|
|
va as u8,
|
|
|
|
|
va & !0xfff,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
((self as *const Self as usize
|
|
|
|
|
- crate::mem::MemoryManager::with(|mm| mm.rpt_base()))
|
|
|
|
|
/ core::mem::size_of::<SwapAlloc>())
|
|
|
|
|
* PAGE_SIZE,
|
|
|
|
|
crate::arch::process::Process::with_current(|p| p.pid().get()),
|
|
|
|
|
);
|
2024-05-14 18:44:11 +00:00
|
|
|
// preserve the wired flag if it was set previously by the loader
|
|
|
|
|
self.vpn = (va as u32) & !0xFFF | pid.get() as u32 | self.vpn & SWAP_FLG_WIRED;
|
2024-05-13 06:15:38 +00:00
|
|
|
} else {
|
|
|
|
|
self.vpn = SWAP_FLG_WIRED | pid.get() as u32;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-05-27 18:41:08 +00:00
|
|
|
#[cfg(feature = "debug-swap-verbose")]
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
println!("-- release of pid{}/{:x}", self.vpn as u8, self.vpn & !0xfff);
|
2024-05-13 06:15:38 +00:00
|
|
|
s.track_alloc(false);
|
2024-05-14 18:44:11 +00:00
|
|
|
// allow the wired flag to clear if the page is actively de-allocated
|
2024-05-13 06:15:38 +00:00
|
|
|
self.vpn = 0;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-15 19:55:41 +00:00
|
|
|
pub unsafe fn touch(&mut self) {
|
|
|
|
|
crate::swap::Swap::with_mut(|s| {
|
|
|
|
|
self.timestamp = s.next_epoch();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 16:28:34 +00:00
|
|
|
#[cfg(feature = "debug-swap-verbose")]
|
|
|
|
|
pub fn get_raw_vpn(&self) -> u32 { self.vpn }
|
|
|
|
|
|
2024-05-15 19:55:41 +00:00
|
|
|
pub fn get_timestamp(&self) -> u32 { self.timestamp }
|
|
|
|
|
|
2024-05-17 16:28:34 +00:00
|
|
|
pub fn set_timestamp(&mut self, val: u32) { self.timestamp = val }
|
|
|
|
|
|
2024-05-13 06:15:38 +00:00
|
|
|
pub unsafe fn reparent(&mut self, pid: PID) {
|
|
|
|
|
self.timestamp = crate::swap::Swap::with_mut(|s| s.next_epoch());
|
|
|
|
|
self.vpn = self.vpn & !&0xFFu32 | pid.get() as u32;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 16:28:34 +00:00
|
|
|
impl PartialEq for SwapAlloc {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.timestamp == other.timestamp && self.timestamp == other.timestamp
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Eq for SwapAlloc {}
|
|
|
|
|
|
|
|
|
|
impl PartialOrd for SwapAlloc {
|
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { self.timestamp.partial_cmp(&other.timestamp) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Ord for SwapAlloc {
|
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() }
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 14:23:51 +00:00
|
|
|
#[cfg(baremetal)]
|
|
|
|
|
#[no_mangle]
|
2024-03-07 20:14:43 +00:00
|
|
|
static mut SWAP: Swap = Swap {
|
|
|
|
|
sid: SID::from_u32(0, 0, 0, 0),
|
|
|
|
|
pc: 0,
|
|
|
|
|
prev_op: None,
|
2024-05-07 16:57:10 +00:00
|
|
|
nested_op: None,
|
2024-03-07 20:14:43 +00:00
|
|
|
swapper_state: 0,
|
2024-03-08 08:20:53 +00:00
|
|
|
swapper_args: [0usize; 8],
|
2024-05-12 08:28:14 +00:00
|
|
|
epoch: 0,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
used_pages: 0,
|
2024-05-13 05:59:17 +00:00
|
|
|
mem_alloc_tracker_paddr: 0,
|
|
|
|
|
mem_alloc_tracker_pages: 0,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
oom_irq_backing: None,
|
2024-05-27 18:41:08 +00:00
|
|
|
clearmem_irq_backing: None,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
unmap_rpt_after_hard_oom: false,
|
2024-05-24 16:06:38 +00:00
|
|
|
oom_thread_backing: None,
|
2024-05-24 17:34:11 +00:00
|
|
|
// hand-tuned based on feedback from actual runtime data
|
|
|
|
|
oom_stack_backing: [0usize; BACKUP_STACK_SIZE_WORDS],
|
2024-03-07 20:14:43 +00:00
|
|
|
};
|
|
|
|
|
|
2024-03-07 14:23:51 +00:00
|
|
|
pub struct Swap {
|
|
|
|
|
/// SID for the swapper
|
|
|
|
|
sid: SID,
|
2024-03-07 20:14:43 +00:00
|
|
|
/// PC for blocking handler
|
|
|
|
|
pc: usize,
|
|
|
|
|
/// previous op
|
|
|
|
|
prev_op: Option<BlockingSwapOp>,
|
2024-05-07 16:57:10 +00:00
|
|
|
/// previous previous op - I think that structurally, we can only get a nesting of depth = 2, and this
|
|
|
|
|
/// is due to things like an alloc advisory pulling in a new superpage while handling the advisory...?
|
|
|
|
|
/// Anyways, there is an assert that checks for the overflow condition of nesting.
|
|
|
|
|
nested_op: Option<BlockingSwapOp>,
|
2024-05-12 08:28:14 +00:00
|
|
|
/// Userspace state pointer for the swapper. This is a PID-2 local virtual address, passed from the
|
|
|
|
|
/// swapper on registration
|
2024-03-07 20:14:43 +00:00
|
|
|
swapper_state: usize,
|
|
|
|
|
/// storage for args
|
2024-03-08 08:20:53 +00:00
|
|
|
swapper_args: [usize; 8],
|
2024-05-12 08:28:14 +00:00
|
|
|
/// swap epoch tracker
|
|
|
|
|
epoch: u32,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
/// approximate used memory, in pages
|
|
|
|
|
used_pages: usize,
|
2024-05-13 05:59:17 +00:00
|
|
|
/// physical base of memory allocation tracker
|
|
|
|
|
mem_alloc_tracker_paddr: usize,
|
|
|
|
|
/// number of pages to map when handling the tracker
|
|
|
|
|
mem_alloc_tracker_pages: usize,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
/// state of IRQ backing before hard OOM
|
|
|
|
|
oom_irq_backing: Option<usize>,
|
2024-05-27 18:41:08 +00:00
|
|
|
clearmem_irq_backing: Option<usize>,
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
/// Keep track of if we should unmap the RPT after a hard OOM. In the case that we hard-OOM while the
|
|
|
|
|
/// soft-OOM handler is running, the RPT will already be mapped into the swapper's space. This is not
|
|
|
|
|
/// uncommon, because the soft-OOM handler is interruptable and can only keep up with gradual memory
|
|
|
|
|
/// demand.
|
|
|
|
|
unmap_rpt_after_hard_oom: bool,
|
2024-05-24 16:06:38 +00:00
|
|
|
/// backing for the thread state that is smashed by the OOM handler
|
|
|
|
|
oom_thread_backing: Option<crate::arch::process::Thread>,
|
2024-05-24 17:34:11 +00:00
|
|
|
/// backing for the stack that is smashed by the OOM handler
|
|
|
|
|
oom_stack_backing: [usize; BACKUP_STACK_SIZE_WORDS],
|
2024-03-07 14:23:51 +00:00
|
|
|
}
|
|
|
|
|
impl Swap {
|
|
|
|
|
pub fn with_mut<F, R>(f: F) -> R
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&mut Swap) -> R,
|
|
|
|
|
{
|
|
|
|
|
#[cfg(baremetal)]
|
|
|
|
|
unsafe {
|
2024-03-29 01:45:49 +00:00
|
|
|
f(&mut *core::ptr::addr_of_mut!(SWAP))
|
2024-03-07 14:23:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(baremetal))]
|
|
|
|
|
SWAP.with(|ss| f(&mut ss.borrow_mut()))
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-06 20:12:46 +00:00
|
|
|
#[cfg(baremetal)]
|
|
|
|
|
pub fn with<F, R>(f: F) -> R
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&Swap) -> R,
|
|
|
|
|
{
|
|
|
|
|
#[cfg(baremetal)]
|
|
|
|
|
unsafe {
|
|
|
|
|
f(&*core::ptr::addr_of!(SWAP))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(baremetal))]
|
|
|
|
|
SWAP.with(|ss| f(&ss.borrow_mut()))
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-12 08:28:14 +00:00
|
|
|
pub fn next_epoch(&mut self) -> u32 {
|
2024-05-17 16:28:34 +00:00
|
|
|
// we save u32::MAX as a search-stop sentinel for the renormalization algorithm
|
|
|
|
|
// we can set this even lower if we want to stress-test epoch renormalization
|
|
|
|
|
if self.epoch < u32::MAX - 1 {
|
|
|
|
|
#[cfg(feature = "debug-swap-verbose")]
|
|
|
|
|
println!("Epoch {}", self.epoch);
|
2024-05-12 08:28:14 +00:00
|
|
|
self.epoch += 1;
|
|
|
|
|
} else {
|
2024-05-17 16:28:34 +00:00
|
|
|
println!("Epoch before renormalization: {:x}", self.epoch);
|
|
|
|
|
// disable interrupts prior to renormalize
|
|
|
|
|
let sim_backing = sim_read();
|
|
|
|
|
sim_write(0x0);
|
|
|
|
|
// safety: this happens only within an exception handler, with interrupts disabled, and thus there
|
|
|
|
|
// should no concurrent access to the underlying data structure. The function itself
|
|
|
|
|
// promises not to create page faults, as it is coded to use only limited stack allocations.
|
|
|
|
|
self.epoch = unsafe {
|
|
|
|
|
crate::mem::renormalize_allocs()
|
|
|
|
|
// enable interrupts after renormalize
|
|
|
|
|
};
|
|
|
|
|
sim_write(sim_backing);
|
|
|
|
|
// the returned value is the greatest used epoch timestamp, so we have to add one for correctness
|
|
|
|
|
self.epoch += 1;
|
|
|
|
|
println!("Epoch after renormalization: {:x}", self.epoch);
|
2024-05-12 08:28:14 +00:00
|
|
|
}
|
2024-05-17 16:28:34 +00:00
|
|
|
self.epoch
|
2024-05-12 08:28:14 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-13 05:59:17 +00:00
|
|
|
pub fn track_alloc(&mut self, is_alloc: bool) {
|
|
|
|
|
if is_alloc {
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
self.used_pages = self.used_pages.saturating_add(1);
|
2024-05-13 05:59:17 +00:00
|
|
|
} else {
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
self.used_pages = self.used_pages.saturating_sub(1);
|
2024-05-13 05:59:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 15:16:47 +00:00
|
|
|
pub fn register_handler(
|
|
|
|
|
&mut self,
|
|
|
|
|
s0: u32,
|
|
|
|
|
s1: u32,
|
|
|
|
|
s2: u32,
|
|
|
|
|
s3: u32,
|
2024-03-07 20:14:43 +00:00
|
|
|
handler: usize,
|
|
|
|
|
state: usize,
|
2024-03-07 15:16:47 +00:00
|
|
|
) -> Result<xous_kernel::Result, xous_kernel::Error> {
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
// initialize used_pages with a comprehensive count of RAM usage
|
2024-05-13 05:59:17 +00:00
|
|
|
let mut total_bytes = 0;
|
|
|
|
|
crate::services::SystemServices::with(|system_services| {
|
|
|
|
|
crate::mem::MemoryManager::with(|mm| {
|
|
|
|
|
for process in &system_services.processes {
|
|
|
|
|
if !process.free() {
|
|
|
|
|
let bytes_used = mm.ram_used_by(process.pid);
|
|
|
|
|
total_bytes += bytes_used;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
self.used_pages = total_bytes / PAGE_SIZE;
|
2024-05-13 05:59:17 +00:00
|
|
|
|
|
|
|
|
// now register the swapper
|
2024-03-07 15:16:47 +00:00
|
|
|
if self.sid == SID::from_u32(0, 0, 0, 0) {
|
|
|
|
|
self.sid = SID::from_u32(s0, s1, s2, s3);
|
2024-03-07 20:14:43 +00:00
|
|
|
self.pc = handler;
|
|
|
|
|
self.swapper_state = state;
|
2024-03-31 09:48:32 +00:00
|
|
|
#[cfg(feature = "debug-swap")]
|
|
|
|
|
println!(
|
|
|
|
|
"handler registered: sid {:?} pc {:?} state {:?}",
|
|
|
|
|
self.sid, self.pc, self.swapper_state
|
|
|
|
|
);
|
2024-05-02 14:08:48 +00:00
|
|
|
Ok(xous_kernel::Result::Scalar5(0, 0, 0, 0, 0))
|
2024-03-07 15:16:47 +00:00
|
|
|
} else {
|
|
|
|
|
// someone is trying to steal the swapper's privileges!
|
2024-03-31 09:48:32 +00:00
|
|
|
#[cfg(feature = "debug-swap")]
|
|
|
|
|
println!("Handler double-register detected!");
|
2024-03-07 15:16:47 +00:00
|
|
|
Err(xous_kernel::Error::AccessDenied)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-13 05:59:17 +00:00
|
|
|
pub fn init_rpt(&mut self, base: usize, pages: usize) {
|
|
|
|
|
self.mem_alloc_tracker_paddr = crate::arch::mem::virt_to_phys(base).unwrap() as usize;
|
|
|
|
|
self.mem_alloc_tracker_pages = pages;
|
|
|
|
|
}
|
2024-03-07 20:14:43 +00:00
|
|
|
|
2024-05-13 09:24:35 +00:00
|
|
|
/// This is a non-divergent syscall (handled entirely within the kernel)
|
|
|
|
|
pub fn get_free_mem(&self) -> SysCallResult {
|
2024-05-24 17:34:11 +00:00
|
|
|
// #[cfg(feature = "debug-swap")]
|
2024-05-13 09:24:35 +00:00
|
|
|
{
|
|
|
|
|
println!("RAM usage:");
|
|
|
|
|
let mut total_bytes = 0;
|
|
|
|
|
crate::services::SystemServices::with(|system_services| {
|
|
|
|
|
crate::mem::MemoryManager::with(|mm| {
|
|
|
|
|
for process in &system_services.processes {
|
|
|
|
|
if !process.free() {
|
|
|
|
|
let bytes_used = mm.ram_used_by(process.pid);
|
|
|
|
|
total_bytes += bytes_used;
|
|
|
|
|
println!(
|
|
|
|
|
" PID {:>3}: {:>4} k {}",
|
|
|
|
|
process.pid,
|
|
|
|
|
bytes_used / 1024,
|
|
|
|
|
system_services.process_name(process.pid).unwrap_or("")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
println!("Via count: {} k total", total_bytes / 1024);
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
println!("Via tracked alloc: {} k total", self.used_pages * PAGE_SIZE / 1024);
|
2024-05-13 09:24:35 +00:00
|
|
|
}
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
let ram_size = crate::mem::MemoryManager::with(|mm| mm.memory_size());
|
|
|
|
|
Ok(xous_kernel::Result::Scalar5(
|
|
|
|
|
ram_size / PAGE_SIZE - self.used_pages,
|
|
|
|
|
ram_size / PAGE_SIZE,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
))
|
2024-05-13 09:24:35 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-24 16:06:38 +00:00
|
|
|
pub fn hard_oom_syscall(&mut self) -> SysCallResult {
|
|
|
|
|
let original_pid = crate::arch::process::current_pid();
|
|
|
|
|
let original_tid = crate::arch::process::current_tid();
|
|
|
|
|
|
|
|
|
|
// move into the swapper's memory space & map the RPT into the swapper's space so it can
|
|
|
|
|
// make decisions about what to move out.
|
|
|
|
|
SystemServices::with(|system_services| {
|
|
|
|
|
let swapper_pid = PID::new(xous_kernel::SWAPPER_PID).unwrap();
|
|
|
|
|
// swap to the swapper space
|
|
|
|
|
let swapper_map = system_services.get_process(swapper_pid).unwrap().mapping;
|
|
|
|
|
swapper_map.activate().unwrap();
|
|
|
|
|
|
|
|
|
|
// map the RPT into userspace
|
|
|
|
|
// note that this technically makes the RPT shared with kernel and userspace, but,
|
|
|
|
|
// the userspace version is read-only, and the next section runs in an interrupt context,
|
|
|
|
|
// so I think it's safe for it to be shared for that duration.
|
|
|
|
|
let mut mapped_pages = false;
|
|
|
|
|
let mut already_mapped = false;
|
|
|
|
|
MemoryManager::with_mut(|mm| {
|
|
|
|
|
for page in 0..self.mem_alloc_tracker_pages {
|
|
|
|
|
let virt = SWAP_RPT_VADDR + page * PAGE_SIZE;
|
|
|
|
|
let entry = unsafe {
|
|
|
|
|
// assume that the soft-OOM handler has run at least once already, so that
|
|
|
|
|
// the L1 PTEs exist. If we hard-OOM before the soft-OOM handler ever runs,
|
|
|
|
|
// the below would panic. We can solve this by having the user space swapper
|
|
|
|
|
// force at least one fetch of the RPT on boot (this is probably a good idea
|
|
|
|
|
// anyways as it ensures that any heap space it needs to handle this is
|
|
|
|
|
// allocated).
|
|
|
|
|
crate::arch::mem::pagetable_entry(virt)
|
|
|
|
|
.expect("Couldn't access PTE for SWAP_RPT_VADDR; ensure soft-OOM handler runs once before hard-OOM")
|
|
|
|
|
.read_volatile()
|
|
|
|
|
};
|
|
|
|
|
// only map pages if they aren't already mapped
|
|
|
|
|
if entry & MMUFlags::VALID.bits() == 0 {
|
|
|
|
|
mapped_pages = true;
|
|
|
|
|
crate::arch::mem::map_page_inner(
|
|
|
|
|
mm,
|
|
|
|
|
swapper_pid,
|
|
|
|
|
self.mem_alloc_tracker_paddr + page * PAGE_SIZE,
|
|
|
|
|
virt,
|
|
|
|
|
xous_kernel::MemoryFlags::R,
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
.ok();
|
|
|
|
|
unsafe { crate::arch::mem::flush_mmu() };
|
|
|
|
|
} else {
|
|
|
|
|
already_mapped = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if mapped_pages && already_mapped {
|
|
|
|
|
// I *think* that whether we map or don't map is always going to be all-or-nothing.
|
|
|
|
|
// However, in the case that only some pages have to be mapped, it probably means that
|
|
|
|
|
// somehow, we hard-OOM'd right in the middle of the page map/unmap routine within
|
|
|
|
|
// the soft-OOM handler. Shouldn't be possible -- `fetch_allocs` immediately disables
|
|
|
|
|
// interrupts -- but let's sanity check this assumption anyways.
|
|
|
|
|
todo!("Need to handle partial RPT maps -- tracking vector required in swapper");
|
|
|
|
|
} else if mapped_pages {
|
|
|
|
|
self.unmap_rpt_after_hard_oom = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "debug-swap")]
|
|
|
|
|
println!("hard_oom - userspace activate");
|
|
|
|
|
// this is safe because we're now in the swapper memory context, thanks to the previous call
|
|
|
|
|
unsafe {
|
|
|
|
|
self.blocking_activate_swapper(BlockingSwapOp::HardOomSyscall(original_tid, original_pid));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-27 18:41:08 +00:00
|
|
|
pub fn swap_stop_irq(&mut self) {
|
|
|
|
|
// disable all IRQs; no context swapping is allowed
|
|
|
|
|
self.oom_irq_backing = Some(sim_read());
|
|
|
|
|
#[cfg(feature = "debug-swap")]
|
|
|
|
|
println!("Swap stored SIM: {:x?}", self.oom_irq_backing);
|
|
|
|
|
sim_write(0x0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn swap_restore_irq(&mut self) {
|
|
|
|
|
// restore IRQ state (don't borrow the system handler's state tracker, so we don't smash
|
|
|
|
|
// it by accident)
|
|
|
|
|
let sim = self.oom_irq_backing.take().expect("Someone stole our IRQ backing!");
|
|
|
|
|
#[cfg(feature = "debug-swap")]
|
|
|
|
|
println!("Swap restoring IRQ: {:x}", sim);
|
|
|
|
|
sim_write(sim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn clearmem_stop_irq(&mut self) {
|
|
|
|
|
self.clearmem_irq_backing = Some(sim_read());
|
|
|
|
|
#[cfg(feature = "debug-swap")]
|
|
|
|
|
println!("Clearmem stored SIM: {:x?}", self.clearmem_irq_backing);
|
|
|
|
|
sim_write(0x0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn clearmem_restore_irq(&mut self) {
|
|
|
|
|
if let Some(sim) = self.clearmem_irq_backing.take() {
|
|
|
|
|
#[cfg(feature = "debug-swap")]
|
|
|
|
|
println!("Clearmem restoring IRQ: {:x}", sim);
|
|
|
|
|
sim_write(sim);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn swap_reentrant_syscall(&mut self, call: xous_kernel::SysCall) {
|
|
|
|
|
self.swap_stop_irq();
|
|
|
|
|
|
2024-05-24 16:06:38 +00:00
|
|
|
// we are in the pid/tid of the invoking process
|
|
|
|
|
// stash the thread state of the current pid/tid because the syscall return trampoline will smash it
|
|
|
|
|
self.oom_thread_backing =
|
|
|
|
|
Some(crate::arch::process::Process::with_current(|p| p.current_thread().clone()));
|
|
|
|
|
|
2024-05-26 04:12:58 +00:00
|
|
|
#[cfg(feature = "debug-swap")]
|
2024-05-24 16:06:38 +00:00
|
|
|
crate::arch::process::Process::with_current(|p| {
|
|
|
|
|
println!(
|
2024-05-27 18:41:08 +00:00
|
|
|
"BEF HANDLER {:x?} {}.{} sepc: {:x} sstatus: {:x?} satp: {:x?}",
|
|
|
|
|
call,
|
2024-05-24 16:06:38 +00:00
|
|
|
p.pid().get(),
|
|
|
|
|
p.current_tid(),
|
|
|
|
|
riscv::register::sepc::read(),
|
|
|
|
|
riscv::register::sstatus::read().spp(),
|
|
|
|
|
riscv::register::satp::read(),
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-24 18:10:55 +00:00
|
|
|
// at this point we are in user pid/tid, but supervisor mode. The current exception handler stack is
|
|
|
|
|
// about to be smashed by the syscall invocation. Save the stack with a specially crafted routine
|
|
|
|
|
// that has the following properties:
|
|
|
|
|
// 1. It does not modify the stack
|
|
|
|
|
// 2. It copies all of the current stack to a backup location
|
|
|
|
|
// 3. It can restore stack after the call is done, also without modifying stack
|
|
|
|
|
//
|
|
|
|
|
// Thus the routine is coded to use just temporary registers (in this case, the argument registers
|
|
|
|
|
// that we know we won't be using in the upcoming re-entrant syscall), and manually checked for
|
|
|
|
|
// no stack manipulations by disassembling the code. It may be necessary to disassemble the code
|
|
|
|
|
// again with future Rust/llvm updates because the behavior of no-stack mods is not guaranteed.
|
|
|
|
|
|
|
|
|
|
// Print the stack pointer for checking purposes
|
2024-05-24 16:53:25 +00:00
|
|
|
let mut current_sp: usize;
|
2024-05-24 16:06:38 +00:00
|
|
|
unsafe {
|
|
|
|
|
core::arch::asm!(
|
|
|
|
|
"mv {current_sp}, sp",
|
|
|
|
|
current_sp = out(reg) current_sp,
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-05-27 18:41:08 +00:00
|
|
|
println!(" --> HANDLER SP extent d'{} bytes", EXCEPTION_STACK_TOP - current_sp);
|
2024-05-24 18:10:55 +00:00
|
|
|
// make a backup copy of the stack
|
2024-05-24 16:06:38 +00:00
|
|
|
let backup_stack_ptr: usize = self.oom_stack_backing.as_ptr() as usize;
|
|
|
|
|
let working_stack_end: usize = EXCEPTION_STACK_TOP;
|
|
|
|
|
let working_stack_ptr: usize =
|
|
|
|
|
EXCEPTION_STACK_TOP - self.oom_stack_backing.len() * core::mem::size_of::<usize>();
|
|
|
|
|
unsafe {
|
|
|
|
|
core::arch::asm!(
|
|
|
|
|
"mv a7, {working_stack_end}",
|
|
|
|
|
"mv a6, {working_stack_ptr}",
|
|
|
|
|
"mv a5, {backup_stack_ptr}",
|
|
|
|
|
"100:",
|
|
|
|
|
"lw a4, 0(a6)",
|
|
|
|
|
"sw a4, 0(a5)",
|
|
|
|
|
"addi a6, a6, 4",
|
|
|
|
|
"addi a5, a5, 4",
|
2024-05-27 18:41:08 +00:00
|
|
|
"blt a6, a7, 100b",
|
2024-05-24 16:06:38 +00:00
|
|
|
"mv {current_sp}, sp",
|
|
|
|
|
|
|
|
|
|
working_stack_end = in(reg) working_stack_end,
|
|
|
|
|
working_stack_ptr = in(reg) working_stack_ptr,
|
|
|
|
|
backup_stack_ptr = in(reg) backup_stack_ptr,
|
|
|
|
|
current_sp = out(reg) current_sp,
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-05-24 18:10:55 +00:00
|
|
|
// this assert will hopefully save us during CI testing if parameters changed and we have to fix them
|
|
|
|
|
// the assert itself would modify stack, but, we're on the path to a panic so that's OK
|
2024-05-24 16:06:38 +00:00
|
|
|
assert!(
|
|
|
|
|
EXCEPTION_STACK_TOP - current_sp <= self.oom_stack_backing.len() * core::mem::size_of::<usize>(),
|
|
|
|
|
"Backing stack not large enough to handle current kernel stack utilization."
|
|
|
|
|
);
|
2024-05-24 18:10:55 +00:00
|
|
|
// invoke this as a nested syscall so it returns here. No stack mods are allowed between the
|
|
|
|
|
// backup and this call
|
2024-05-27 18:41:08 +00:00
|
|
|
xous_kernel::rsyscall(call).ok();
|
2024-05-24 16:06:38 +00:00
|
|
|
|
2024-05-24 18:10:55 +00:00
|
|
|
// restore the stack immediately after the syscall return (no debug info etc. allowed as it may
|
|
|
|
|
// rely on stack).
|
2024-05-24 16:06:38 +00:00
|
|
|
let restore_stack_ptr: usize = self.oom_stack_backing.as_ptr() as usize;
|
|
|
|
|
let working_restore_end: usize = EXCEPTION_STACK_TOP;
|
|
|
|
|
let working_restore_ptr: usize =
|
|
|
|
|
EXCEPTION_STACK_TOP - self.oom_stack_backing.len() * core::mem::size_of::<usize>();
|
|
|
|
|
unsafe {
|
|
|
|
|
core::arch::asm!(
|
|
|
|
|
"mv a7, {working_restore_end}",
|
|
|
|
|
"mv a6, {working_restore_ptr}",
|
|
|
|
|
"mv a5, {restore_stack_ptr}",
|
|
|
|
|
"100:",
|
|
|
|
|
"lw a4, 0(a5)",
|
|
|
|
|
"sw a4, 0(a6)",
|
|
|
|
|
"addi a6, a6, 4",
|
|
|
|
|
"addi a5, a5, 4",
|
2024-05-27 18:41:08 +00:00
|
|
|
"blt a6, a7, 100b",
|
2024-05-24 16:06:38 +00:00
|
|
|
|
|
|
|
|
working_restore_end = in(reg) working_restore_end,
|
|
|
|
|
working_restore_ptr = in(reg) working_restore_ptr,
|
|
|
|
|
restore_stack_ptr = in(reg) restore_stack_ptr,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-26 04:12:58 +00:00
|
|
|
#[cfg(feature = "debug-swap")]
|
2024-05-24 16:06:38 +00:00
|
|
|
crate::arch::process::Process::with_current(|p| {
|
|
|
|
|
println!(
|
2024-05-27 18:41:08 +00:00
|
|
|
"AFT HANDLER {}.{} sepc: {:x} sstatus: {:x?} satp: {:x?}",
|
2024-05-24 16:06:38 +00:00
|
|
|
p.pid().get(),
|
|
|
|
|
p.current_tid(),
|
|
|
|
|
riscv::register::sepc::read(),
|
|
|
|
|
riscv::register::sstatus::read().spp(),
|
|
|
|
|
riscv::register::satp::read(),
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-24 18:10:55 +00:00
|
|
|
// then restore the smashed thread backing state on return from the syscall
|
2024-05-24 16:06:38 +00:00
|
|
|
crate::arch::process::Process::with_current_mut(|p| {
|
|
|
|
|
*p.current_thread_mut() =
|
|
|
|
|
self.oom_thread_backing.take().expect("No thread backing was set prior to OOM handler")
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-27 18:41:08 +00:00
|
|
|
self.swap_restore_irq();
|
2024-05-24 16:06:38 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-13 09:24:35 +00:00
|
|
|
/// The address space on entry to `retrieve_page` is `target_pid`; it must ensure
|
|
|
|
|
/// that the address space is still `target_pid` on return.
|
|
|
|
|
///
|
|
|
|
|
/// Also takes as argument the virtual address of the target page in the target PID,
|
|
|
|
|
/// as well as the physical address of the page.
|
|
|
|
|
///
|
2024-05-15 19:55:41 +00:00
|
|
|
/// Note that the originating caller to this will update the epoch of the memory
|
|
|
|
|
/// alloc counter, so, we don't have to update it explicitly her (as we do with
|
|
|
|
|
/// evict_page)
|
|
|
|
|
///
|
2024-05-13 09:24:35 +00:00
|
|
|
/// This call diverges into the userspace swapper.
|
2024-05-14 18:44:11 +00:00
|
|
|
/// Divergent calls must turn of IRQs before memory spaces are changed.
|
2024-05-27 18:41:08 +00:00
|
|
|
pub fn retrieve_page_syscall(&mut self, target_vaddr_in_pid: usize, paddr: usize) -> ! {
|
|
|
|
|
let target_pid = crate::arch::process::current_pid();
|
|
|
|
|
let target_tid = crate::arch::process::current_tid();
|
2024-05-14 20:26:08 +00:00
|
|
|
|
2024-05-13 09:24:35 +00:00
|
|
|
let block_vaddr_in_swap =
|
|
|
|
|
crate::arch::mem::map_page_to_swapper(paddr).expect("couldn't map target page to swapper");
|
|
|
|
|
// we are now in the swapper's memory space
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "debug-swap")]
|
2024-05-13 13:00:17 +00:00
|
|
|
println!(
|
implement hard-OOM handler
A hard-OOM is what happens when the allocator really runs out
of memory. To review, there are two types of OOMs handled by
the swapper:
- "OOM Doom": impending OOM. This is detected by a userspace
process that can do more sophisticated processing and
algorithms to predict an impending OOM. The
OOM Doom handler is also interruptable, which allows other
progresses to make progress while memory is being cleared up,
hopefully allowing for a more graceful failure curve.
- "Hard OOM": Literally no more pages are available. This is
triggered when the page allocator can't map a single more page.
The hard-OOM handler turns off all interrupts, and all the OOM
process happens within the uninterruptable userspace context.
This effectively turns the OOM handler "inside out" compared to
the normal, interruptable userspace handler. In order to support
this, a set of calls that implement the kernel space pre-amble
and post-amble (i.e. StealPage and ReleaseMemory) are introduced.
This does introduce an extra edge case in the exit path of
the hard-OOM, namely, if the hard-OOM failed on a page that was
swapped. Here, after clearing memory, we have an empty page,
but no data; we have to again re-enter the userspace handler to
fill that page. The twist here is we have to remember to re-instate
interrupts on this edge case otherwise we lose pre-emption.
2024-05-20 17:13:13 +00:00
|
|
|
"retrieve_page - userspace activate from pid{:?}/tid{:?} for vaddr {:x?} -> paddr {:x?}",
|
|
|
|
|
target_pid, target_tid, target_vaddr_in_pid, paddr
|
2024-05-13 13:00:17 +00:00
|
|
|
);
|
2024-05-13 09:24:35 +00:00
|
|
|
// this is safe because map_page_to_swapper() leaves us in the swapper memory context
|
|
|
|
|
unsafe {
|
|
|
|
|
self.blocking_activate_swapper(BlockingSwapOp::ReadFromSwap(
|
|
|
|
|
target_pid,
|
|
|
|
|
target_tid,
|
|
|
|
|
target_vaddr_in_pid,
|
|
|
|
|
block_vaddr_in_swap,
|
|
|
|
|
paddr,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-14 18:44:11 +00:00
|
|
|
/// Safety:
|
|
|
|
|
/// - the current page table mapping context must be PID 2 (the swapper's PID) for this to work
|
|
|
|
|
/// - interrupts must have been disabled prior to setting the context to PID 2
|
2024-03-07 20:14:43 +00:00
|
|
|
/// `op` contains the opcode data
|
|
|
|
|
/// `payload_ptr` is the pointer to the virtual address of the swapped block in PID2 space
|
2024-03-08 10:31:39 +00:00
|
|
|
unsafe fn blocking_activate_swapper(&mut self, op: BlockingSwapOp) -> ! {
|
2024-03-07 20:14:43 +00:00
|
|
|
// setup the argument block
|
|
|
|
|
match op {
|
remember tid for swap-read; do PID dance for resume abstraction
We want to use the finish_callback_and_resume() function because
it's well-tested and sets up everything just right, but it expects
that we're coming from another process. Unfortunately, in the
middle of all of this, we need to map a page back into the
destination process' memory space, so we end up starting from
the swapper's memory space, changing into the destination process'
memory space, then mapping the page, then changing back into
the swapper's memory space, then using the finish_callback_and_resume()
call.
I think the impact might not be *that* bad, mainly it's a TLB
miss and the PTW isn't super bad, and this comes after a disastrously
expensive decrypt-from-RAM operation -- so probably not worth it
to angonize over optimizing this bit at this point, and stick
with well-tested primitives.
2024-05-03 17:54:23 +00:00
|
|
|
BlockingSwapOp::ReadFromSwap(pid, _tid, vaddr_in_pid, vaddr_in_swap, _paddr) => {
|
2024-03-08 10:31:39 +00:00
|
|
|
self.swapper_args[0] = self.swapper_state;
|
2024-05-13 09:24:35 +00:00
|
|
|
self.swapper_args[1] = 1; // ReadFromSwap
|
2024-03-08 10:31:39 +00:00
|
|
|
self.swapper_args[2] = pid.get() as usize;
|
|
|
|
|
self.swapper_args[3] = vaddr_in_pid;
|
|
|
|
|
self.swapper_args[4] = vaddr_in_swap;
|
|
|
|
|
}
|
2024-05-24 16:06:38 +00:00
|
|
|
BlockingSwapOp::HardOomSyscall(_tid, _pid) => {
|
|
|
|
|
self.swapper_args[0] = self.swapper_state;
|
|
|
|
|
self.swapper_args[1] = 3; // HardOom
|
|
|
|
|
}
|
2024-03-07 20:14:43 +00:00
|
|
|
}
|
2024-05-07 16:57:10 +00:00
|
|
|
if let Some(op) = self.prev_op.take() {
|
2024-05-07 23:20:33 +00:00
|
|
|
if let Some(dop) = self.nested_op {
|
2024-05-27 18:41:08 +00:00
|
|
|
println!("ERR: nesting depth of 2 exceeded! {:x?}", dop);
|
2024-05-07 23:20:33 +00:00
|
|
|
panic!("Nesting depth of 2 exceeded!");
|
|
|
|
|
}
|
2024-05-27 18:41:08 +00:00
|
|
|
println!("Nesting {:x?}", op);
|
2024-05-07 16:57:10 +00:00
|
|
|
self.nested_op = Some(op);
|
2024-05-27 18:41:08 +00:00
|
|
|
panic!(
|
|
|
|
|
"Nesting should not happen - this code is vestigial but remains to see if this edge case remains"
|
|
|
|
|
);
|
2024-05-07 16:57:10 +00:00
|
|
|
}
|
2024-05-13 05:59:17 +00:00
|
|
|
// println!("Setting prev_op to {:?}", op);
|
2024-03-07 20:14:43 +00:00
|
|
|
self.prev_op = Some(op);
|
|
|
|
|
let swapper_pid: PID = PID::new(xous_kernel::SWAPPER_PID).unwrap();
|
|
|
|
|
|
2024-05-14 18:44:11 +00:00
|
|
|
// Prepare to enter into the swapper space "as if it were an IRQ"
|
|
|
|
|
// Disable all other IRQs and redirect into userspace
|
2024-03-07 20:14:43 +00:00
|
|
|
SystemServices::with_mut(|ss| {
|
2024-05-14 20:26:08 +00:00
|
|
|
if !crate::arch::irq::is_handling_irq() {
|
|
|
|
|
ss.make_callback_to(
|
|
|
|
|
swapper_pid,
|
|
|
|
|
self.pc as *const usize,
|
|
|
|
|
crate::services::CallbackType::Swap(self.swapper_args),
|
|
|
|
|
)
|
|
|
|
|
.expect("couldn't switch to handler");
|
|
|
|
|
} else {
|
|
|
|
|
ss.make_callback_to(
|
|
|
|
|
swapper_pid,
|
|
|
|
|
self.pc as *const usize,
|
|
|
|
|
crate::services::CallbackType::SwapInIrq(self.swapper_args),
|
|
|
|
|
)
|
|
|
|
|
.expect("couldn't switch to handler");
|
|
|
|
|
};
|
|
|
|
|
});
|
2024-03-07 20:14:43 +00:00
|
|
|
|
2024-05-14 18:44:11 +00:00
|
|
|
// The current process/TID is now setup, "resume" into the handler
|
2024-03-07 20:14:43 +00:00
|
|
|
crate::services::ArchProcess::with_current_mut(|process| {
|
|
|
|
|
crate::arch::syscall::resume(current_pid().get() == 1, process.current_thread())
|
2024-03-08 10:31:39 +00:00
|
|
|
})
|
2024-03-07 20:14:43 +00:00
|
|
|
// the call above diverges; the return end of things is inside the IRQ handler, where we
|
|
|
|
|
// conduct business as if we're returning from a syscall.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Cleanup after `blocking_activate_swapper()` - called on return from the divergence at the end
|
|
|
|
|
/// of the previous call.
|
2024-03-08 10:31:39 +00:00
|
|
|
///
|
|
|
|
|
/// Safety: this call must only be invoked in the swapper's memory context
|
|
|
|
|
pub unsafe fn exit_blocking_call(&mut self) -> Result<xous_kernel::Result, xous_kernel::Error> {
|
2024-05-27 18:41:08 +00:00
|
|
|
let (pid, tid) = match self.prev_op.take() {
|
2024-05-14 18:44:11 +00:00
|
|
|
// Called from any process. Resume as if recovering from a page fault; absorb the rest of the
|
2024-05-27 18:41:08 +00:00
|
|
|
// page fault handler code into this routine at the point where it would have diverged.
|
|
|
|
|
Some(BlockingSwapOp::ReadFromSwap(pid, tid, _vaddr_in_pid, vaddr_in_swap, _paddr)) => {
|
2024-03-08 10:31:39 +00:00
|
|
|
MemoryManager::with_mut(|mm| {
|
|
|
|
|
// we are in the swapper's memory space a this point
|
|
|
|
|
// unmap the page from the swapper
|
2024-05-27 18:41:08 +00:00
|
|
|
crate::arch::mem::unmap_page_inner(mm, vaddr_in_swap)
|
|
|
|
|
.expect("couldn't unmap page lent to swapper");
|
|
|
|
|
// the page map into the target space happens after the syscall returns
|
|
|
|
|
});
|
|
|
|
|
(pid, tid)
|
2024-03-07 20:14:43 +00:00
|
|
|
}
|
2024-05-24 16:06:38 +00:00
|
|
|
Some(BlockingSwapOp::HardOomSyscall(tid, pid)) => {
|
|
|
|
|
// We enter this from the swapper's memory space
|
|
|
|
|
assert!(
|
|
|
|
|
crate::arch::process::current_pid().get() == SWAPPER_PID,
|
|
|
|
|
"Hard OOM syscall did not return from swapper's space"
|
|
|
|
|
);
|
|
|
|
|
// unmap RPT from swapper's userspace, if it was previously unmapped
|
|
|
|
|
if self.unmap_rpt_after_hard_oom {
|
|
|
|
|
MemoryManager::with_mut(|mm| {
|
|
|
|
|
for page in 0..self.mem_alloc_tracker_pages {
|
|
|
|
|
crate::arch::mem::unmap_page_inner(mm, SWAP_RPT_VADDR + page * PAGE_SIZE).ok();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-05-27 18:41:08 +00:00
|
|
|
(pid, tid)
|
|
|
|
|
}
|
|
|
|
|
None => panic!("No previous swap op was set"),
|
|
|
|
|
};
|
2024-05-24 16:06:38 +00:00
|
|
|
|
2024-05-27 18:41:08 +00:00
|
|
|
// return to the original pid memory space, now that we have memory
|
|
|
|
|
SystemServices::with_mut(|system_services| {
|
|
|
|
|
// Cleanup the swapper
|
|
|
|
|
system_services.finish_swap();
|
2024-05-24 16:06:38 +00:00
|
|
|
|
2024-05-27 18:41:08 +00:00
|
|
|
if !crate::arch::irq::is_handling_irq() {
|
|
|
|
|
system_services.swap_resume_to_userspace(pid, tid).expect("couldn't swap_resume");
|
|
|
|
|
}
|
2024-05-24 16:06:38 +00:00
|
|
|
|
2024-05-27 18:41:08 +00:00
|
|
|
// Switch to target process
|
|
|
|
|
let process = system_services.get_process_mut(pid).unwrap();
|
|
|
|
|
process.mapping.activate().unwrap();
|
|
|
|
|
process.activate().unwrap();
|
|
|
|
|
// Activate the current context
|
|
|
|
|
crate::arch::process::Process::current().set_tid(tid).unwrap();
|
|
|
|
|
process.current_thread = tid;
|
|
|
|
|
});
|
2024-05-24 16:06:38 +00:00
|
|
|
|
2024-05-27 18:41:08 +00:00
|
|
|
// return to kernel space -- these calls can only be originated from kernel space
|
|
|
|
|
unsafe { riscv::register::sstatus::set_spp(riscv::register::sstatus::SPP::Supervisor) };
|
2024-05-24 16:06:38 +00:00
|
|
|
|
2024-05-07 23:20:33 +00:00
|
|
|
if let Some(op) = self.nested_op.take() {
|
2024-05-27 18:41:08 +00:00
|
|
|
println!("Popping nested_op into prev_op with {:?}", op);
|
2024-05-07 16:57:10 +00:00
|
|
|
self.prev_op = Some(op);
|
2024-03-07 20:14:43 +00:00
|
|
|
}
|
2024-05-27 18:41:08 +00:00
|
|
|
Ok(xous_kernel::Result::Scalar5(0, 0, 0, 0, 0))
|
2024-03-07 20:14:43 +00:00
|
|
|
}
|
2024-03-07 14:23:51 +00:00
|
|
|
}
|
2024-05-14 18:44:11 +00:00
|
|
|
|
2024-05-17 16:28:34 +00:00
|
|
|
// Various in-line assembly thunks go below this line.
|
|
|
|
|
fn sim_read() -> usize {
|
|
|
|
|
let existing: usize;
|
|
|
|
|
unsafe { core::arch::asm!("csrrs {0}, 0x9C0, zero", out(reg) existing) };
|
|
|
|
|
existing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn sim_write(new: usize) { unsafe { core::arch::asm!("csrrw zero, 0x9C0, {0}", in(reg) new) }; }
|