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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user