refactor write page syscall for explicitly managed writing

this is a much simpler, race condition-free version of flash
writing which has the penalty of requiring a copy of the page
to be made by the caller before the write. However the
copy is mostly already paid for because on the initial access
to the page, we had to copy the flash into RAM.
This commit is contained in:
bunnie 2025-05-18 04:23:53 +08:00
parent 788efb3056
commit dfd82c94b8

View file

@ -3,10 +3,10 @@
use core::cmp::Ordering;
use loader::SWAP_FLG_DIRTY;
use loader::SWAP_FLG_WIRED;
use xous_kernel::SWAPPER_PID;
use xous_kernel::arch::EXCEPTION_STACK_TOP;
use xous_kernel::arch::MMAP_VIRT_BASE;
use xous_kernel::arch::PAGE_SIZE;
use xous_kernel::arch::SWAP_RPT_VADDR;
use xous_kernel::{PID, SysCallResult, TID};
@ -40,8 +40,7 @@ pub enum SwapAbi {
HardOom = 4,
StealPage = 5,
ReleaseMemory = 6,
MarkDirty = 7,
Sync = 8,
WritePage = 7,
}
/// SYNC WITH `xous-swapper/src/main.rs`
impl SwapAbi {
@ -54,8 +53,7 @@ impl SwapAbi {
4 => HardOom,
5 => StealPage,
6 => ReleaseMemory,
7 => MarkDirty,
8 => Sync,
7 => WritePage,
_ => Invalid,
}
}
@ -71,7 +69,7 @@ pub enum BlockingSwapOp {
ReadFromSwap(PID, TID, usize, usize, usize),
/// PID of the target block, current paddr of block, original vaddr in the space of target block PID,
/// physical address of block. Returns to PID of the target block.
WriteToFlash(PID, TID, usize, usize, usize),
WriteToFlash(PID, TID, PID, usize, usize),
/// 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
@ -154,12 +152,6 @@ impl SwapAlloc {
pub fn set_wired(&mut self) { self.vpn |= SWAP_FLG_WIRED; }
pub fn set_dirty(&mut self) { self.vpn |= SWAP_FLG_DIRTY; }
pub fn clear_dirty(&mut self) { self.vpn &= !SWAP_FLG_DIRTY; }
pub fn is_dirty(&self) -> bool { self.vpn & SWAP_FLG_DIRTY == SWAP_FLG_DIRTY }
#[cfg(feature = "debug-swap-verbose")]
pub fn get_raw_vpn(&self) -> u32 { self.vpn }
@ -684,33 +676,30 @@ impl Swap {
}
}
/// This passes on to the userspace handler a page that needs to be written to SPI flash.
pub fn flush_page_syscall(
pub fn write_page_syscall(
&mut self,
target_vaddr_in_pid: usize,
paddr: usize,
target_pid: PID,
target_tid: usize,
src_pid: PID,
flash_offset: usize,
page_vaddr_in_swapper: usize,
) -> ! {
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
let target_pid = crate::arch::process::current_pid();
let target_tid = crate::arch::process::current_tid();
// #[cfg(feature = "debug-swap-verbose")]
println!(
"flush_page - userspace activate from pid{:?}/tid{:?} for vaddr {:x?} -> paddr {:x?}",
target_pid, target_tid, target_vaddr_in_pid, paddr
"write_page - userspace activate for pid{:?}/tid{:?} for vaddr {:x?} -> offset {:x?}",
target_pid, target_tid, page_vaddr_in_swapper, flash_offset
);
// prevent context switching to avoid re-entrant calls while handling a call
self.swap_stop_irq();
// this is safe because map_page_to_swapper() leaves us in the swapper memory context
// this is safe because the syscall pre-amble checks that we're in the swapper context
unsafe {
self.blocking_activate_swapper(BlockingSwapOp::WriteToFlash(
target_pid,
target_tid,
target_vaddr_in_pid,
block_vaddr_in_swap,
paddr,
src_pid,
page_vaddr_in_swapper,
flash_offset,
));
}
}
@ -730,12 +719,11 @@ impl Swap {
self.swapper_args[3] = vaddr_in_pid;
self.swapper_args[4] = vaddr_in_swap;
}
BlockingSwapOp::WriteToFlash(pid, _tid, vaddr_in_pid, vaddr_in_swap, _paddr) => {
BlockingSwapOp::WriteToFlash(_pid, _tid, _src_pid, vaddr_in_swap, flash_offset) => {
self.swapper_args[0] = self.swapper_state;
self.swapper_args[1] = 4; // WriteToFlash
self.swapper_args[2] = pid.get() as usize;
self.swapper_args[3] = vaddr_in_pid;
self.swapper_args[4] = vaddr_in_swap;
self.swapper_args[2] = vaddr_in_swap;
self.swapper_args[3] = flash_offset;
}
BlockingSwapOp::HardOomSyscall(_tid, _pid) => {
self.swapper_args[0] = self.swapper_state;
@ -805,18 +793,29 @@ impl Swap {
}
// Called from any process. Clear the dirty bit on the RPT when exiting. No pages are unmapped
// by this routine, that would be handled by the OOMer, if at all.
Some(BlockingSwapOp::WriteToFlash(pid, tid, _vaddr_in_pid, vaddr_in_swap, paddr)) => {
Some(BlockingSwapOp::WriteToFlash(pid, tid, src_pid, _vpage_addr_in_swapper, flash_offset)) => {
// this works because the V:P mapping for the flash memory is 1:1 for the LSBs
let flash_vaddr = MMAP_VIRT_BASE + flash_offset;
// evict_page_inner marks the page as swapped/invalid in src_pid, but also maps the page
// into the swapper's address space
let swap_vaddr = crate::arch::mem::evict_page_inner(src_pid, flash_vaddr).unwrap();
// release the page from the swapper's address space
MemoryManager::with_mut(|mm| {
// we are in the swapper's memory space a this point
// unmap the page from the swapper
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
let paddr = crate::arch::mem::virt_to_phys(swap_vaddr).unwrap() as usize;
// #[cfg(feature = "debug-swap-verbose")]
println!("Release flash backing page - paddr {:x}", paddr);
// this call unmaps the virtual page from the page table
crate::arch::mem::unmap_page_inner(mm, swap_vaddr).expect("couldn't unmap page");
// This call releases the physical page from the RPT - the pid has to match that of
// the original owner. This is the "pointy end" of the stick;
// after this call, the memory is now back into the free pool.
mm.release_page_swap(paddr as *mut usize, src_pid)
.expect("couldn't free page that was swapped out");
});
// mark the physical page as clean
MemoryManager::with_mut(|mm| mm.mark_rpt_clean(paddr).expect("couldn't mark RPT as clean"));
// Unhalt IRQs
println!("WriteToFlash exit - restore IRQ");
self.swap_restore_irq();
(pid, tid)
}