feat(kernel): seL4-style IPC, notifications, status ABI

- badges + Mint, Call/Reply/ReplyRecv w/ gen-checked reply slot
- cap transfer (1/msg, copy, needs Delegate, rechecked at delivery)
- notifications: signal/wait/poll/bind, irq -> notification routing
- WaitAny/WaitForInterrupt removed; a0=status, ErrorCode
- libkos: single ecall primitive, compat wrappers + typed new api
- fix: trap return clobbered user t0 on every trap
- fix: wait queues FIFO, delegate keeps badge, fault cap checked
- docs/ipc.md spec, ipc tests, ipc fuzz, kani ntfn + waitqueue
This commit is contained in:
2026-10-26 20:27:38 +01:00
parent d43421e33e
commit 76f10ff1f1
46 changed files with 3339 additions and 1296 deletions

View File

@@ -75,7 +75,6 @@ _restore_smode:
ld x1, 8(sp)
ld x3, 24(sp)
ld x4, 32(sp)
ld x5, 40(sp)
ld x6, 48(sp)
ld x7, 56(sp)
ld x8, 64(sp)
@@ -103,13 +102,19 @@ _restore_smode:
ld x30, 240(sp)
ld x31, 248(sp)
addi sp, sp, 272
# Every GPR except t0 (x5) is restored. Decide where we return to while
# t0 is still free, and restore it last on each path; using t0 after
# its restore would corrupt the interrupted program's t0.
csrr t0, sstatus
andi t0, t0, 0x100
bnez t0, _sret_smode
ld x5, 40(sp)
addi sp, sp, 272
csrrw sp, sscratch, sp
sret
_sret_smode:
ld x5, 40(sp)
addi sp, sp, 272
sret
"#
);
@@ -139,156 +144,138 @@ pub fn set_kernel_stack(kernel_sp: usize) {
}
}
/// Decodes the syscall in `frame`, or `None` for an unknown number or an
/// argument that is not representable (e.g. a slot out of range).
fn decode_syscall(frame: &TrapFrame) -> Option<domain::syscall::Syscall> {
use domain::message::Message;
use domain::syscall::{Syscall, WaitSet};
use domain::syscall::Syscall;
use types::address::{DataRef, EntryPoint, StackPointer, VirtualAddress};
use types::badge::Badge;
use types::bounded::{CapSlot, PageCount};
use types::permission::PermissionSet;
use types::syscall_abi::*;
let nr = frame.regs[17]; // a7
let a0 = frame.regs[10];
let a1 = frame.regs[11];
let a2 = frame.regs[12];
let a3 = frame.regs[13];
let a4 = frame.regs[14];
let a = |i: usize| frame.regs[10 + i];
let slot = |i: usize| CapSlot::new(u8::try_from(a(i)).ok()?);
let message = || -> Option<Message> {
let msg = Message::from_words([a(1), a(2), a(3), a(4)]);
Some(match a(5) {
NONE => msg,
_ => msg.with_cap(slot(5)?),
})
};
match nr {
SYS_EXIT => Some(Syscall::Exit),
SYS_HALT => Some(Syscall::Halt),
SYS_EXIT_THREAD => Some(Syscall::ExitThread),
SYS_YIELD => Some(Syscall::Yield),
SYS_GET_TIME => Some(Syscall::GetTime),
SYS_GET_PROCESS_ID => Some(Syscall::GetProcessId),
SYS_SEND => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
let msg = Message::from_words([a1, a2, a3, a4]);
Some(Syscall::Send {
endpoint: slot,
message: msg,
})
}
SYS_RECEIVE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::Receive { endpoint: slot })
}
SYS_DELEGATE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
let to = types::bounded::CapSlot::new(a1 as u8)?;
let perms = types::permission::PermissionSet::from_bits(a2 as u8);
Some(Syscall::Delegate {
slot,
to_endpoint: to,
permissions: perms,
})
}
SYS_WAIT_FOR_INTERRUPT => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::WaitForInterrupt { irq: slot })
}
SYS_ACKNOWLEDGE_INTERRUPT => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::AcknowledgeInterrupt { irq: slot })
}
SYS_WAIT_ANY => {
let ep0 = if a0 != usize::MAX {
types::bounded::CapSlot::new(a0 as u8)
} else {
None
};
let ep1 = if a1 != usize::MAX {
types::bounded::CapSlot::new(a1 as u8)
} else {
None
};
let ep2 = if a2 != usize::MAX {
types::bounded::CapSlot::new(a2 as u8)
} else {
None
};
let ep3 = if a3 != usize::MAX {
types::bounded::CapSlot::new(a3 as u8)
} else {
None
};
let sources = WaitSet::new([ep0, ep1, ep2, ep3], [None, None, None, None]);
Some(Syscall::WaitAny { sources })
}
SYS_CREATE_PROCESS => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::CreateProcess {
create_cap: slot,
image: types::address::DataRef::empty(),
})
}
SYS_KILL => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::Kill { process: slot })
}
SYS_REVOKE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::Revoke { slot })
}
SYS_SPAWN_THREAD => Some(Syscall::SpawnThread {
entry: types::address::EntryPoint::from_raw(a0),
stack: types::address::StackPointer::from_raw(a1),
}),
SYS_REQUEST_PAGES => {
let count = types::bounded::PageCount::new(a0 as u16)?;
Some(Syscall::RequestPages { count })
}
SYS_RELEASE_PAGES => {
let count = types::bounded::PageCount::new(a1 as u16)?;
Some(Syscall::ReleasePages {
addr: types::address::VirtualAddress::from_raw(a0),
count,
})
}
SYS_SPAWN_THREAD_IN => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::SpawnThreadIn {
process: slot,
entry: types::address::EntryPoint::from_raw(a1),
stack: types::address::StackPointer::from_raw(a2),
})
}
SYS_CREATE_ENDPOINT => Some(Syscall::CreateEndpoint),
SYS_DROP_CAP => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::DropCap { slot })
}
SYS_DEBUG_PUTCHAR => Some(Syscall::DebugPutchar { byte: a0 as u8 }),
SYS_MAP_PAGE => {
let slot = types::bounded::CapSlot::new(a0 as u8)?;
Some(Syscall::MapPage {
process: slot,
src_va: types::address::VirtualAddress::from_raw(a1),
dst_va: types::address::VirtualAddress::from_raw(a2),
executable: a3 != 0,
})
}
_ => None,
}
Some(match nr {
SYS_EXIT => Syscall::Exit,
SYS_HALT => Syscall::Halt,
SYS_EXIT_THREAD => Syscall::ExitThread,
SYS_YIELD => Syscall::Yield,
SYS_GET_TIME => Syscall::GetTime,
SYS_GET_PROCESS_ID => Syscall::GetProcessId,
SYS_SEND => Syscall::Send {
endpoint: slot(0)?,
message: message()?,
},
SYS_CALL => Syscall::Call {
endpoint: slot(0)?,
message: message()?,
},
SYS_RECEIVE => Syscall::Receive { endpoint: slot(0)? },
SYS_REPLY => Syscall::Reply {
message: message()?,
},
SYS_REPLY_RECV => Syscall::ReplyRecv {
endpoint: slot(0)?,
message: message()?,
},
SYS_DELEGATE => Syscall::Delegate {
slot: slot(0)?,
to_endpoint: slot(1)?,
permissions: PermissionSet::from_bits(u8::try_from(a(2)).ok()?),
},
SYS_MINT => Syscall::Mint {
slot: slot(0)?,
badge: Badge::new(a(1)),
permissions: PermissionSet::from_bits(u8::try_from(a(2)).ok()?),
},
SYS_CREATE_NOTIFICATION => Syscall::CreateNotification,
SYS_SIGNAL => Syscall::Signal {
notification: slot(0)?,
},
SYS_WAIT => Syscall::Wait {
notification: slot(0)?,
},
SYS_POLL => Syscall::Poll {
notification: slot(0)?,
},
SYS_BIND_NOTIFICATION => Syscall::BindNotification {
notification: slot(0)?,
},
SYS_SET_IRQ_NOTIFICATION => Syscall::SetIrqNotification {
irq: slot(0)?,
notification: slot(1)?,
},
SYS_ACKNOWLEDGE_INTERRUPT => Syscall::AcknowledgeInterrupt { irq: slot(0)? },
SYS_CREATE_PROCESS => Syscall::CreateProcess {
create_cap: slot(0)?,
image: DataRef::empty(),
},
SYS_KILL => Syscall::Kill { process: slot(0)? },
SYS_REVOKE => Syscall::Revoke { slot: slot(0)? },
SYS_DROP_CAP => Syscall::DropCap { slot: slot(0)? },
SYS_SPAWN_THREAD => Syscall::SpawnThread {
entry: EntryPoint::from_raw(a(0)),
stack: StackPointer::from_raw(a(1)),
},
SYS_REQUEST_PAGES => Syscall::RequestPages {
count: PageCount::new(u16::try_from(a(0)).ok()?)?,
},
SYS_RELEASE_PAGES => Syscall::ReleasePages {
addr: VirtualAddress::from_raw(a(0)),
count: PageCount::new(u16::try_from(a(1)).ok()?)?,
},
SYS_SPAWN_THREAD_IN => Syscall::SpawnThreadIn {
process: slot(0)?,
entry: EntryPoint::from_raw(a(1)),
stack: StackPointer::from_raw(a(2)),
},
SYS_CREATE_ENDPOINT => Syscall::CreateEndpoint,
SYS_DEBUG_PUTCHAR => Syscall::DebugPutchar { byte: a(0) as u8 },
SYS_MAP_PAGE => Syscall::MapPage {
process: slot(0)?,
src_va: VirtualAddress::from_raw(a(1)),
dst_va: VirtualAddress::from_raw(a(2)),
executable: a(3) != 0,
},
_ => return None,
})
}
fn write_result(frame: &mut TrapFrame, outcome: &kernel::SyscallOutcome) {
use kernel::{SyscallOutcome, SyscallResult};
/// Writes a completed syscall's result per the register ABI: status in
/// `a0`, results in `a1..`.
fn write_result(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
use kernel::SyscallResult;
use kernel::syscall::DeliveryKind;
use types::syscall_abi::{KIND_CALL, KIND_MESSAGE, KIND_SIGNAL, NONE};
match outcome {
SyscallOutcome::Complete(result) => match result {
SyscallResult::Ok => frame.regs[10] = 0,
SyscallResult::Value(v) => frame.regs[10] = *v,
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
SyscallResult::Message(m) => {
frame.regs[10] = m.word(0);
frame.regs[11] = m.word(1);
frame.regs[12] = m.word(2);
frame.regs[13] = m.word(3);
}
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
},
SyscallOutcome::BlockCaller(_) => {}
SyscallOutcome::SwitchTo(_) => frame.regs[10] = 0,
SyscallOutcome::TerminateCaller => {}
let r = &mut frame.regs;
r[10] = 0;
match result {
SyscallResult::Ok => {}
SyscallResult::Value(v) => r[11] = *v,
SyscallResult::Slot(s) => r[11] = s.value() as usize,
SyscallResult::Received(d) => {
r[11] = d.badge;
r[12..16].copy_from_slice(&d.words);
r[16] = d.cap.map_or(NONE, |s| s.value() as usize);
r[17] = match d.kind {
DeliveryKind::Message => KIND_MESSAGE,
DeliveryKind::Call => KIND_CALL,
DeliveryKind::Signal => KIND_SIGNAL,
};
}
SyscallResult::Error(e) => r[10] = kernel::syscall::error_code(e) as usize,
}
}
@@ -310,24 +297,7 @@ fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFra
fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) {
let kern = unsafe { &mut *crate::kernel_ptr() };
if let Some(result) = kern.take_pending_result(tid) {
write_result_direct(frame, &result);
}
}
fn write_result_direct(frame: &mut TrapFrame, result: &kernel::SyscallResult) {
use kernel::SyscallResult;
match result {
SyscallResult::Ok => frame.regs[10] = 0,
SyscallResult::Value(v) => frame.regs[10] = *v,
SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize,
SyscallResult::Message(m) => {
frame.regs[10] = m.word(0);
frame.regs[11] = m.word(1);
frame.regs[12] = m.word(2);
frame.regs[13] = m.word(3);
}
SyscallResult::Error(_) => frame.regs[10] = usize::MAX,
SyscallResult::WakeReason(_) => frame.regs[10] = 0,
write_result(frame, &result);
}
}
@@ -441,14 +411,17 @@ fn handle_exception(frame: &mut TrapFrame, scause: usize) {
CAUSE_ECALL_U => {
frame.sepc += 4;
if let Some(syscall) = decode_syscall(frame) {
let Some(syscall) = decode_syscall(frame) else {
frame.regs[10] = types::syscall_abi::ErrorCode::UnknownSyscall as usize;
return;
};
{
let kern = unsafe { &mut *crate::kernel_ptr() };
let current_tid = kern.current_thread();
let outcome = kern.handle_syscall(syscall);
write_result(frame, &outcome);
match outcome {
kernel::SyscallOutcome::Complete(_) => {}
kernel::SyscallOutcome::Complete(result) => write_result(frame, &result),
kernel::SyscallOutcome::BlockCaller(_) => {
if let Some(tid) = current_tid {
switch_to_next(frame, tid);