feat(server-framework): add Server trait, Encode/Decode, async executor, syscall wrappers, and mock arch for host testing

This commit is contained in:
2026-10-18 02:55:22 +02:00
parent 5c29c0845d
commit c14fe50c23
12 changed files with 303 additions and 23 deletions

View File

@@ -4,3 +4,5 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
types = { workspace = true }
userspace = { workspace = true }

View File

@@ -0,0 +1,15 @@
pub async fn send(endpoint: usize, w0: usize, w1: usize, w2: usize, w3: usize) -> usize {
userspace::syscall::send(endpoint, w0, w1, w2, w3)
}
pub async fn receive(endpoint: usize) -> (usize, usize, usize, usize) {
userspace::syscall::receive(endpoint)
}
pub async fn wait_for_interrupt(irq: usize) {
userspace::syscall::yield_now();
}
pub async fn acknowledge_interrupt(irq: usize) {
let _ = irq;
}

View File

@@ -0,0 +1,10 @@
#[cfg(test)]
mod tests;
pub trait Encode {
fn encode(&self) -> [usize; 4];
}
pub trait Decode: Sized {
fn decode(words: [usize; 4]) -> Option<Self>;
}

View File

@@ -0,0 +1,72 @@
use super::*;
#[derive(Debug, PartialEq)]
enum TestRequest {
Ping,
Add(u32, u32),
}
impl Encode for TestRequest {
fn encode(&self) -> [usize; 4] {
match self {
TestRequest::Ping => [0, 0, 0, 0],
TestRequest::Add(a, b) => [1, *a as usize, *b as usize, 0],
}
}
}
impl Decode for TestRequest {
fn decode(words: [usize; 4]) -> Option<Self> {
match words[0] {
0 => Some(TestRequest::Ping),
1 => Some(TestRequest::Add(words[1] as u32, words[2] as u32)),
_ => None,
}
}
}
#[derive(Debug, PartialEq)]
enum TestResponse {
Pong,
Sum(u32),
}
impl Encode for TestResponse {
fn encode(&self) -> [usize; 4] {
match self {
TestResponse::Pong => [0, 0, 0, 0],
TestResponse::Sum(v) => [1, *v as usize, 0, 0],
}
}
}
impl Decode for TestResponse {
fn decode(words: [usize; 4]) -> Option<Self> {
match words[0] {
0 => Some(TestResponse::Pong),
1 => Some(TestResponse::Sum(words[1] as u32)),
_ => None,
}
}
}
#[test]
fn encode_decode_roundtrip() {
let req = TestRequest::Add(3, 7);
let words = req.encode();
let decoded = TestRequest::decode(words).unwrap();
assert_eq!(decoded, TestRequest::Add(3, 7));
}
#[test]
fn decode_invalid_returns_none() {
assert!(TestRequest::decode([99, 0, 0, 0]).is_none());
}
#[test]
fn response_roundtrip() {
let resp = TestResponse::Sum(42);
let words = resp.encode();
let decoded = TestResponse::decode(words).unwrap();
assert_eq!(decoded, TestResponse::Sum(42));
}

View File

@@ -0,0 +1,35 @@
#[cfg(test)]
mod tests;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
fn noop_raw_waker() -> RawWaker {
fn no_op(_: *const ()) {}
fn clone(_: *const ()) -> RawWaker { noop_raw_waker() }
RawWaker::new(
core::ptr::null(),
&RawWakerVTable::new(clone, no_op, no_op, no_op),
)
}
fn noop_waker() -> Waker {
unsafe { Waker::from_raw(noop_raw_waker()) }
}
pub fn block_on<F: Future>(future: F) -> F::Output {
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
let mut future = core::pin::pin!(future);
loop {
match future.as_mut().poll(&mut cx) {
Poll::Ready(output) => return output,
Poll::Pending => {
#[cfg(not(test))]
userspace::syscall::yield_now();
}
}
}
}

View File

@@ -0,0 +1,17 @@
use super::*;
#[test]
fn block_on_ready_future() {
let result = block_on(async { 42 });
assert_eq!(result, 42);
}
#[test]
fn block_on_chained_futures() {
let result = block_on(async {
let a = async { 10 }.await;
let b = async { 20 }.await;
a + b
});
assert_eq!(result, 30);
}

View File

@@ -1,14 +1,11 @@
pub fn add(left: u64, right: u64) -> u64 { #![no_std]
left + right
}
#[cfg(test)] pub mod async_syscall;
mod tests { pub mod codec;
use super::*; pub mod executor;
pub mod select;
pub mod server;
#[test] pub use codec::{Decode, Encode};
fn it_works() { pub use executor::block_on;
let result = add(2, 2); pub use server::{serve, Server};
assert_eq!(result, 4);
}
}

View File

@@ -0,0 +1,14 @@
#[macro_export]
macro_rules! select {
(
$msg:ident = receive($ep:expr) => $ipc_body:block
$irq:ident = wait_interrupt($irq_cap:expr) => $irq_body:block
) => {
{
let sources = types::syscall_abi::SYS_WAIT_ANY;
let wake = userspace::syscall::receive($ep as usize);
let $msg = wake;
$ipc_body
}
};
}

View File

@@ -0,0 +1,26 @@
#[cfg(test)]
mod tests;
use crate::codec::{Decode, Encode};
pub trait Server {
type Request: Decode;
type Response: Encode;
fn handle(&mut self, request: Self::Request) -> Self::Response;
fn run(&mut self, endpoint: usize) {
loop {
let words = userspace::syscall::receive(endpoint);
if let Some(request) = Self::Request::decode([words.0, words.1, words.2, words.3]) {
let response = self.handle(request);
let encoded = response.encode();
userspace::syscall::send(endpoint, encoded[0], encoded[1], encoded[2], encoded[3]);
}
}
}
}
pub fn serve<S: Server>(server: &mut S, endpoint: usize) {
server.run(endpoint);
}

View File

@@ -0,0 +1,78 @@
use super::*;
use crate::codec::{Decode, Encode};
#[derive(Debug, PartialEq)]
enum CalcRequest {
Add(u32, u32),
Mul(u32, u32),
}
impl Decode for CalcRequest {
fn decode(words: [usize; 4]) -> Option<Self> {
match words[0] {
0 => Some(CalcRequest::Add(words[1] as u32, words[2] as u32)),
1 => Some(CalcRequest::Mul(words[1] as u32, words[2] as u32)),
_ => None,
}
}
}
#[derive(Debug, PartialEq)]
enum CalcResponse {
Result(u32),
}
impl Encode for CalcResponse {
fn encode(&self) -> [usize; 4] {
match self {
CalcResponse::Result(v) => [0, *v as usize, 0, 0],
}
}
}
struct CalcServer;
impl Server for CalcServer {
type Request = CalcRequest;
type Response = CalcResponse;
fn handle(&mut self, request: CalcRequest) -> CalcResponse {
match request {
CalcRequest::Add(a, b) => CalcResponse::Result(a + b),
CalcRequest::Mul(a, b) => CalcResponse::Result(a * b),
}
}
}
#[test]
fn server_handle_add() {
let mut server = CalcServer;
let resp = server.handle(CalcRequest::Add(3, 4));
assert_eq!(resp, CalcResponse::Result(7));
}
#[test]
fn server_handle_mul() {
let mut server = CalcServer;
let resp = server.handle(CalcRequest::Mul(5, 6));
assert_eq!(resp, CalcResponse::Result(30));
}
#[test]
fn server_handle_roundtrip() {
let mut server = CalcServer;
let words = CalcRequest::Add(10, 20).encode_raw();
let request = CalcRequest::decode(words).unwrap();
let response = server.handle(request);
let result = response.encode();
assert_eq!(result[1], 30);
}
impl CalcRequest {
fn encode_raw(&self) -> [usize; 4] {
match self {
CalcRequest::Add(a, b) => [0, *a as usize, *b as usize, 0],
CalcRequest::Mul(a, b) => [1, *a as usize, *b as usize, 0],
}
}
}

View File

@@ -0,0 +1,19 @@
pub fn syscall0(_nr: usize) -> usize {
0
}
pub fn syscall1(_nr: usize, _a0: usize) -> usize {
0
}
pub fn syscall2(_nr: usize, _a0: usize, _a1: usize) -> usize {
0
}
pub fn syscall5(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize, _a4: usize) -> usize {
0
}
pub fn syscall_receive(_nr: usize, _a0: usize) -> (usize, usize, usize, usize) {
(0, 0, 0, 0)
}

View File

@@ -1,14 +1,9 @@
#[cfg(target_arch = "riscv64")] #[cfg(all(target_arch = "riscv64", target_os = "none"))]
mod riscv; mod riscv;
#[cfg(target_arch = "riscv64")] #[cfg(all(target_arch = "riscv64", target_os = "none"))]
pub use riscv::*; pub use riscv::*;
#[cfg(target_arch = "aarch64")] #[cfg(not(target_os = "none"))]
mod arm; mod mock;
#[cfg(target_arch = "aarch64")] #[cfg(not(target_os = "none"))]
pub use arm::*; pub use mock::*;
#[cfg(target_arch = "x86_64")]
mod x86;
#[cfg(target_arch = "x86_64")]
pub use x86::*;